deskbuddy 0.2.0-beta.3 → 0.2.0-beta.5

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 writeFileSync14, rmSync as rmSync9 } 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 randomBytes6 } 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
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":
@@ -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");
@@ -2469,18 +2616,18 @@ var SessionQueue = class {
2469
2616
  };
2470
2617
 
2471
2618
  // src/daemon/remote.ts
2472
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync10, renameSync as renameSync7, writeFileSync as writeFileSync9 } from "fs";
2473
- 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";
2474
2621
  function handleDir() {
2475
- return join13(deskbuddyHome(), "remote");
2622
+ return join14(deskbuddyHome(), "remote");
2476
2623
  }
2477
2624
  function handlePath(tool) {
2478
2625
  const safe = (tool || "").replace(/[^0-9a-zA-Z_-]/g, "");
2479
- return join13(handleDir(), `${safe || "unknown"}.json`);
2626
+ return join14(handleDir(), `${safe || "unknown"}.json`);
2480
2627
  }
2481
2628
  function readHandle(tool) {
2482
2629
  try {
2483
- const h = JSON.parse(readFileSync10(handlePath(tool), "utf8"));
2630
+ const h = JSON.parse(readFileSync11(handlePath(tool), "utf8"));
2484
2631
  if (typeof h?.sessionId !== "string" || !h.sessionId) return null;
2485
2632
  if (typeof h?.cwd !== "string" || !h.cwd) return null;
2486
2633
  return h;
@@ -2490,10 +2637,10 @@ function readHandle(tool) {
2490
2637
  }
2491
2638
  function writeHandle(tool, handle) {
2492
2639
  try {
2493
- mkdirSync10(handleDir(), { recursive: true });
2640
+ mkdirSync11(handleDir(), { recursive: true });
2494
2641
  const tmp = handlePath(tool) + ".tmp";
2495
- writeFileSync9(tmp, JSON.stringify(handle), "utf8");
2496
- renameSync7(tmp, handlePath(tool));
2642
+ writeFileSync10(tmp, JSON.stringify(handle), "utf8");
2643
+ renameSync8(tmp, handlePath(tool));
2497
2644
  } catch {
2498
2645
  }
2499
2646
  }
@@ -2546,11 +2693,11 @@ function failureMessage(reason) {
2546
2693
  }
2547
2694
 
2548
2695
  // src/daemon/seen.ts
2549
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync11, renameSync as renameSync8, writeFileSync as writeFileSync10 } from "fs";
2550
- 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";
2551
2698
  var SEEN_CAPACITY = 200;
2552
2699
  function seenPath() {
2553
- return join14(deskbuddyHome(), "seen.json");
2700
+ return join15(deskbuddyHome(), "seen.json");
2554
2701
  }
2555
2702
  var SeenIds = class _SeenIds {
2556
2703
  #ids = [];
@@ -2581,7 +2728,7 @@ var SeenIds = class _SeenIds {
2581
2728
  }
2582
2729
  static load() {
2583
2730
  try {
2584
- const raw = JSON.parse(readFileSync11(seenPath(), "utf8"));
2731
+ const raw = JSON.parse(readFileSync12(seenPath(), "utf8"));
2585
2732
  return new _SeenIds(Array.isArray(raw?.ids) ? raw.ids.filter((x) => typeof x === "string") : []);
2586
2733
  } catch {
2587
2734
  return new _SeenIds();
@@ -2589,10 +2736,10 @@ var SeenIds = class _SeenIds {
2589
2736
  }
2590
2737
  save() {
2591
2738
  try {
2592
- mkdirSync11(deskbuddyHome(), { recursive: true });
2739
+ mkdirSync12(deskbuddyHome(), { recursive: true });
2593
2740
  const tmp = seenPath() + ".tmp";
2594
- writeFileSync10(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
2595
- renameSync8(tmp, seenPath());
2741
+ writeFileSync11(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
2742
+ renameSync9(tmp, seenPath());
2596
2743
  } catch {
2597
2744
  }
2598
2745
  }
@@ -3254,151 +3401,398 @@ var CodexWatch = class {
3254
3401
  };
3255
3402
 
3256
3403
  // src/core/codexRemote.ts
3257
- import { mkdirSync as mkdirSync12, renameSync as renameSync9, rmSync as rmSync4, writeFileSync as writeFileSync11 } from "fs";
3258
- 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";
3259
3406
  function codexRemotePath() {
3260
- return join15(deskbuddyHome(), "codex-remote.json");
3407
+ return join16(deskbuddyHome(), "codex-remote.json");
3261
3408
  }
3262
3409
  function writeCodexRemote(threads, pid = process.pid, now = Date.now()) {
3263
3410
  try {
3264
- mkdirSync12(deskbuddyHome(), { recursive: true });
3411
+ mkdirSync13(deskbuddyHome(), { recursive: true });
3265
3412
  const p = codexRemotePath();
3266
- writeFileSync11(p + ".tmp", JSON.stringify({ pid, threads: [...threads], at: now }), "utf8");
3267
- renameSync9(p + ".tmp", p);
3413
+ writeFileSync12(p + ".tmp", JSON.stringify({ pid, threads: [...threads], at: now }), "utf8");
3414
+ renameSync10(p + ".tmp", p);
3268
3415
  } catch {
3269
3416
  }
3270
3417
  }
3271
3418
  function removeCodexRemote() {
3272
3419
  try {
3273
- rmSync4(codexRemotePath(), { force: true });
3420
+ rmSync5(codexRemotePath(), { force: true });
3274
3421
  } catch {
3275
3422
  }
3276
3423
  }
3277
3424
 
3278
3425
  // src/wrap/channel.ts
3279
- 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";
3280
3428
  import { connect, createServer as createServer2 } from "net";
3281
- var MAX_LINE_BYTES = 64 * 1024;
3282
- function lines(sock, onLine) {
3283
- let buf = "";
3284
- sock.setEncoding("utf8");
3285
- sock.on("data", (d) => {
3286
- buf += d;
3287
- if (buf.length > MAX_LINE_BYTES) {
3288
- sock.destroy();
3289
- return;
3290
- }
3291
- let i;
3292
- while ((i = buf.indexOf("\n")) >= 0) {
3293
- const line = buf.slice(0, i);
3294
- buf = buf.slice(i + 1);
3295
- if (line.trim()) onLine(line);
3296
- }
3297
- });
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");
3298
3438
  }
3299
- function parseRequest(line) {
3300
- let m;
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;
3301
3457
  try {
3302
- m = JSON.parse(line);
3458
+ process.kill(pid, 0);
3459
+ return true;
3303
3460
  } catch {
3304
- return null;
3461
+ return false;
3305
3462
  }
3306
- if (!m || typeof m !== "object") return null;
3307
- const instructionId = typeof m.instructionId === "string" ? m.instructionId : "";
3308
- const sessionId = typeof m.sessionId === "string" ? m.sessionId : "";
3309
- if (!isValidId(instructionId) || !isValidId(sessionId)) return null;
3310
- const text = sanitizeText(typeof m.text === "string" ? m.text : "");
3311
- if (!text || text.length > MAX_TEXT_LENGTH) return null;
3312
- const expireAt = typeof m.expireAt === "number" && Number.isFinite(m.expireAt) ? m.expireAt : 0;
3313
- return { instructionId, sessionId, text, expireAt };
3314
3463
  }
3315
- function startChannelServer(path, onRequest, platform = process.platform) {
3316
- return new Promise((resolve5) => {
3317
- const server = createServer2((sock) => {
3318
- let open = true;
3319
- sock.on("error", () => {
3320
- });
3321
- sock.on("close", () => open = false);
3322
- const reply = (r) => {
3323
- if (sock.destroyed || !sock.writable) return;
3324
- try {
3325
- sock.write(JSON.stringify(r) + "\n");
3326
- } catch {
3327
- }
3328
- };
3329
- lines(sock, (line) => {
3330
- const req2 = parseRequest(line);
3331
- if (!req2) {
3332
- reply({ instructionId: "", status: "rejected", reason: "bad_request" });
3333
- return;
3334
- }
3335
- onRequest(req2, reply, () => open);
3336
- });
3337
- });
3338
- if (platform !== "win32") {
3339
- try {
3340
- rmSync5(path, { force: true });
3341
- } catch {
3342
- }
3343
- }
3344
- const oldMask = platform === "win32" ? null : process.umask(127);
3345
- const restore = () => {
3346
- if (oldMask !== null) process.umask(oldMask);
3347
- };
3348
- server.once("error", () => {
3349
- restore();
3350
- resolve5(null);
3351
- });
3352
- server.listen(path, () => {
3353
- restore();
3354
- if (platform !== "win32") {
3355
- try {
3356
- chmodSync2(path, 384);
3357
- } catch {
3358
- }
3359
- }
3360
- server.on("error", () => {
3361
- });
3362
- resolve5(server);
3363
- });
3364
- });
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
+ }
3365
3475
  }
3366
- function closeChannelServer(server, path, platform = process.platform) {
3476
+ function removeWakeMark(sessionId) {
3477
+ const safe = safeSid(sessionId);
3478
+ if (!safe) return;
3367
3479
  try {
3368
- server?.close();
3480
+ rmSync6(join17(marksDir(), `${safe}.json`), { force: true });
3369
3481
  } catch {
3370
3482
  }
3371
- if (platform !== "win32") {
3483
+ }
3484
+ function clearWakeMarks() {
3485
+ let names = [];
3486
+ try {
3487
+ names = readdirSync3(marksDir());
3488
+ } catch {
3489
+ return;
3490
+ }
3491
+ for (const n of names) {
3372
3492
  try {
3373
- rmSync5(path, { force: true });
3493
+ rmSync6(join17(marksDir(), n), { force: true });
3374
3494
  } catch {
3375
3495
  }
3376
3496
  }
3377
3497
  }
3378
- var FINAL = /* @__PURE__ */ new Set(["started", "rejected", "failed", "not_here"]);
3379
- function sendToWindow(path, req2, onReply, opts = {}) {
3380
- return new Promise((resolve5) => {
3381
- let settled = false;
3382
- let handled = false;
3383
- let done = false;
3384
- const settle = (v) => {
3385
- if (settled) return;
3386
- settled = true;
3387
- handled = v === "handled";
3388
- clearTimeout(timer);
3389
- resolve5(v);
3390
- };
3391
- let sock;
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") {
3392
3523
  try {
3393
- sock = connect(path);
3524
+ execFileSync7("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
3394
3525
  } catch {
3395
- resolve5("fallback");
3396
- return;
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";
3397
3528
  }
3398
- const timer = setTimeout(() => {
3399
- settle("fallback");
3400
- sock.destroy();
3401
- }, opts.ackTimeoutMs ?? 3e3);
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
3634
+ var MAX_LINE_BYTES = 64 * 1024;
3635
+ function lines(sock, onLine) {
3636
+ let buf = "";
3637
+ sock.setEncoding("utf8");
3638
+ sock.on("data", (d) => {
3639
+ buf += d;
3640
+ if (buf.length > MAX_LINE_BYTES) {
3641
+ sock.destroy();
3642
+ return;
3643
+ }
3644
+ let i;
3645
+ while ((i = buf.indexOf("\n")) >= 0) {
3646
+ const line = buf.slice(0, i);
3647
+ buf = buf.slice(i + 1);
3648
+ if (line.trim()) onLine(line);
3649
+ }
3650
+ });
3651
+ }
3652
+ function parseRequest(line) {
3653
+ let m;
3654
+ if (typeof line === "string") {
3655
+ try {
3656
+ m = JSON.parse(line);
3657
+ } catch {
3658
+ return null;
3659
+ }
3660
+ } else {
3661
+ m = line;
3662
+ }
3663
+ if (!m || typeof m !== "object") return null;
3664
+ const instructionId = typeof m.instructionId === "string" ? m.instructionId : "";
3665
+ const sessionId = typeof m.sessionId === "string" ? m.sessionId : "";
3666
+ if (!isValidId(instructionId) || sessionId !== "" && !isValidId(sessionId)) return null;
3667
+ const text = sanitizeText(typeof m.text === "string" ? m.text : "");
3668
+ if (!text || text.length > MAX_TEXT_LENGTH) return null;
3669
+ const expireAt = typeof m.expireAt === "number" && Number.isFinite(m.expireAt) ? m.expireAt : 0;
3670
+ return { instructionId, sessionId, text, expireAt };
3671
+ }
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) {
3677
+ return new Promise((resolve5) => {
3678
+ const server = createServer2((sock) => {
3679
+ let open = true;
3680
+ let phase = "hello";
3681
+ let nonceC = "";
3682
+ let nonceS = "";
3683
+ sock.on("error", () => {
3684
+ });
3685
+ sock.on("close", () => open = false);
3686
+ const authTimer = setTimeout(() => {
3687
+ if (phase === "hello") sock.destroy();
3688
+ }, 5e3);
3689
+ authTimer.unref?.();
3690
+ const raw = (m) => {
3691
+ if (sock.destroyed || !sock.writable) return;
3692
+ try {
3693
+ sock.write(JSON.stringify(m) + "\n");
3694
+ } catch {
3695
+ }
3696
+ };
3697
+ const reply = (r) => raw({ ...r, mac: replyMac(secret, nonceC, r) });
3698
+ lines(sock, (line) => {
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();
3723
+ }
3724
+ });
3725
+ });
3726
+ if (platform !== "win32") {
3727
+ try {
3728
+ rmSync7(path, { force: true });
3729
+ } catch {
3730
+ }
3731
+ }
3732
+ const oldMask = platform === "win32" ? null : process.umask(127);
3733
+ const restore = () => {
3734
+ if (oldMask !== null) process.umask(oldMask);
3735
+ };
3736
+ server.once("error", () => {
3737
+ restore();
3738
+ resolve5(null);
3739
+ });
3740
+ server.listen(path, () => {
3741
+ restore();
3742
+ if (platform !== "win32") {
3743
+ try {
3744
+ chmodSync3(path, 384);
3745
+ } catch {
3746
+ }
3747
+ }
3748
+ server.on("error", () => {
3749
+ });
3750
+ resolve5(server);
3751
+ });
3752
+ });
3753
+ }
3754
+ function closeChannelServer(server, path, platform = process.platform) {
3755
+ try {
3756
+ server?.close();
3757
+ } catch {
3758
+ }
3759
+ if (platform !== "win32") {
3760
+ try {
3761
+ rmSync7(path, { force: true });
3762
+ } catch {
3763
+ }
3764
+ }
3765
+ }
3766
+ var FINAL = /* @__PURE__ */ new Set(["started", "rejected", "failed", "not_here"]);
3767
+ function sendToWindow(path, secret, req2, onReply, opts = {}) {
3768
+ return new Promise((resolve5) => {
3769
+ if (!secret) {
3770
+ resolve5("fallback");
3771
+ return;
3772
+ }
3773
+ let settled = false;
3774
+ let handled = false;
3775
+ let done = false;
3776
+ const nonceC = randomBytes5(16).toString("hex");
3777
+ let nonceS = "";
3778
+ const settle = (v) => {
3779
+ if (settled) return;
3780
+ settled = true;
3781
+ handled = v === "handled";
3782
+ clearTimeout(timer);
3783
+ resolve5(v);
3784
+ };
3785
+ let sock;
3786
+ try {
3787
+ sock = connect(path);
3788
+ } catch {
3789
+ resolve5("fallback");
3790
+ return;
3791
+ }
3792
+ const timer = setTimeout(() => {
3793
+ settle("fallback");
3794
+ sock.destroy();
3795
+ }, opts.ackTimeoutMs ?? 3e3);
3402
3796
  sock.on("error", () => {
3403
3797
  if (!settled) settle("fallback");
3404
3798
  });
@@ -3413,16 +3807,33 @@ function sendToWindow(path, req2, onReply, opts = {}) {
3413
3807
  }
3414
3808
  });
3415
3809
  sock.on("connect", () => {
3416
- sock.write(JSON.stringify(req2) + "\n");
3810
+ sock.write(JSON.stringify({ t: "hello", nonceC }) + "\n");
3417
3811
  });
3418
3812
  lines(sock, (line) => {
3419
- let r;
3813
+ let m;
3420
3814
  try {
3421
- r = JSON.parse(line);
3815
+ m = JSON.parse(line);
3422
3816
  } catch {
3817
+ sock.destroy();
3423
3818
  return;
3424
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");
3828
+ return;
3829
+ }
3830
+ const { mac, ...rest } = m;
3831
+ const r = rest;
3425
3832
  if (r.instructionId !== req2.instructionId) return;
3833
+ if (!macEqual(mac, replyMac(secret, nonceC, r))) {
3834
+ sock.destroy();
3835
+ return;
3836
+ }
3426
3837
  if (!settled) {
3427
3838
  if (r.status === "ack") {
3428
3839
  settle("handled");
@@ -3444,40 +3855,97 @@ function sendToWindow(path, req2, onReply, opts = {}) {
3444
3855
  });
3445
3856
  }
3446
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 TRUST_RE = /trust (the files in )?this folder|Yes, I trust this folder/i;
3877
+ var PLACEHOLDER_RE = /^Try ".*"$/;
3878
+ function isTerminalReport(s) {
3879
+ if (!s) return true;
3880
+ const rest = s.replace(/\x1b\[[IO]/g, "").replace(/\x1b\[\d+;\d+R/g, "").replace(/\x1b\[[?>][\d;]*\$?[a-zA-Z]/g, "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1bP[^\x1b]*\x1b\\/g, "");
3881
+ return rest === "";
3882
+ }
3883
+ var PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
3884
+ function inspectLines(lines2, undimmed) {
3885
+ const text = lines2.join("\n");
3886
+ const trust = TRUST_RE.test(text);
3887
+ const permission = PERMISSION_RE.test(text);
3888
+ const rules = [];
3889
+ lines2.forEach((l, i) => {
3890
+ if (/^─{20,}/.test(l.trim())) rules.push(i);
3891
+ });
3892
+ let input = null;
3893
+ if (rules.length >= 2) {
3894
+ const a = rules[rules.length - 2];
3895
+ const b = rules[rules.length - 1];
3896
+ const src = undimmed && undimmed.length === lines2.length ? undimmed : lines2;
3897
+ input = src.slice(a + 1, b).map((l) => l.trimEnd()).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
3898
+ }
3899
+ const v = { permission, input };
3900
+ if (trust) v.trust = true;
3901
+ if (input !== null && PLACEHOLDER_RE.test(input.trim())) v.placeholder = true;
3902
+ return v;
3903
+ }
3904
+ function boxMatches(view, body) {
3905
+ if (view.input === null || view.permission) return false;
3906
+ const flat = (x) => x.replace(/\s+/g, "");
3907
+ return flat(view.input) === flat(body) || /\[Pasted text #\d+/.test(view.input);
3908
+ }
3909
+
3447
3910
  // src/core/wrapWindows.ts
3448
- 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";
3449
- import { tmpdir } from "os";
3450
- import { join as join16 } from "path";
3911
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync15, readdirSync as readdirSync4, readFileSync as readFileSync14, renameSync as renameSync12, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync14 } from "fs";
3912
+ import { tmpdir as tmpdir2 } from "os";
3913
+ import { join as join18 } from "path";
3451
3914
  var WINDOW_ID_RE = /^[a-f0-9]{32}$/;
3452
3915
  function windowsDir() {
3453
- return join16(deskbuddyHome(), "windows");
3916
+ return join18(deskbuddyHome(), "windows");
3454
3917
  }
3455
3918
  function bySessionDir() {
3456
- return join16(windowsDir(), "by-session");
3919
+ return join18(windowsDir(), "by-session");
3457
3920
  }
3458
- function safeSid(sessionId) {
3921
+ function safeSid2(sessionId) {
3459
3922
  return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
3460
3923
  }
3461
3924
  function ensureDir(dir) {
3462
- mkdirSync13(dir, { recursive: true, mode: 448 });
3925
+ mkdirSync15(dir, { recursive: true, mode: 448 });
3463
3926
  }
3464
3927
  function atomicWrite(file, text) {
3465
3928
  const tmp = `${file}.${process.pid}.tmp`;
3466
- writeFileSync12(tmp, text, { encoding: "utf8", mode: 384 });
3467
- renameSync10(tmp, file);
3929
+ writeFileSync14(tmp, text, { encoding: "utf8", mode: 384 });
3930
+ renameSync12(tmp, file);
3468
3931
  }
3469
3932
  function windowFile(windowId) {
3470
- return join16(windowsDir(), `${windowId}.json`);
3933
+ return join18(windowsDir(), `${windowId}.json`);
3471
3934
  }
3472
3935
  function writeWindow(windowId, rec) {
3473
3936
  if (!WINDOW_ID_RE.test(windowId)) return;
3474
3937
  ensureDir(windowsDir());
3475
3938
  atomicWrite(windowFile(windowId), JSON.stringify(rec));
3939
+ restrictToUser(windowFile(windowId));
3940
+ }
3941
+ function windowSecret(windowId) {
3942
+ const s = readWindow(windowId)?.secret;
3943
+ return typeof s === "string" && /^[0-9a-f]{64}$/.test(s) ? Buffer.from(s, "hex") : null;
3476
3944
  }
3477
3945
  function readWindow(windowId) {
3478
3946
  if (!WINDOW_ID_RE.test(windowId)) return null;
3479
3947
  try {
3480
- const r = JSON.parse(readFileSync12(windowFile(windowId), "utf8"));
3948
+ const r = JSON.parse(readFileSync14(windowFile(windowId), "utf8"));
3481
3949
  return r && typeof r.pid === "number" ? r : null;
3482
3950
  } catch {
3483
3951
  return null;
@@ -3487,273 +3955,259 @@ function removeWindow(windowId) {
3487
3955
  if (!WINDOW_ID_RE.test(windowId)) return;
3488
3956
  for (const f of [windowFile(windowId), lastEventFile(windowId)]) {
3489
3957
  try {
3490
- rmSync6(f, { force: true });
3958
+ rmSync8(f, { force: true });
3491
3959
  } catch {
3492
3960
  }
3493
3961
  }
3494
3962
  let names = [];
3495
3963
  try {
3496
- names = readdirSync2(bySessionDir());
3964
+ names = readdirSync4(bySessionDir());
3497
3965
  } catch {
3498
3966
  return;
3499
3967
  }
3500
3968
  for (const n of names) {
3501
- const f = join16(bySessionDir(), n);
3969
+ const f = join18(bySessionDir(), n);
3502
3970
  try {
3503
- if (readFileSync12(f, "utf8").trim() === windowId) rmSync6(f, { force: true });
3971
+ if (readFileSync14(f, "utf8").trim() === windowId) rmSync8(f, { force: true });
3504
3972
  } catch {
3505
3973
  }
3506
3974
  }
3507
3975
  }
3508
3976
  function readBySession(sessionId) {
3509
- const safe = safeSid(sessionId);
3977
+ const safe = safeSid2(sessionId);
3510
3978
  if (!safe) return null;
3511
3979
  try {
3512
- const id = readFileSync12(join16(bySessionDir(), safe), "utf8").trim();
3980
+ const id = readFileSync14(join18(bySessionDir(), safe), "utf8").trim();
3513
3981
  return WINDOW_ID_RE.test(id) ? id : null;
3514
3982
  } catch {
3515
3983
  return null;
3516
3984
  }
3517
3985
  }
3518
3986
  function lastEventFile(windowId) {
3519
- return join16(windowsDir(), `${windowId}.last`);
3987
+ return join18(windowsDir(), `${windowId}.last`);
3520
3988
  }
3521
3989
  function readLastEvent(windowId) {
3522
3990
  if (!WINDOW_ID_RE.test(windowId)) return null;
3523
3991
  try {
3524
- const e = JSON.parse(readFileSync12(lastEventFile(windowId), "utf8"));
3992
+ const e = JSON.parse(readFileSync14(lastEventFile(windowId), "utf8"));
3525
3993
  return e && typeof e.ev === "string" && typeof e.at === "number" ? { ev: e.ev, sid: String(e.sid ?? ""), at: e.at } : null;
3526
3994
  } catch {
3527
3995
  return null;
3528
- }
3529
- }
3530
- function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
3531
- if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
3532
- const name = `${windowId.slice(0, 16)}.sock`;
3533
- const primary = join16(home, "win", name);
3534
- if (Buffer.byteLength(primary) <= 103) return primary;
3535
- const uid = typeof process.getuid === "function" ? process.getuid() : 0;
3536
- return join16(tmpdir(), `deskbuddy-${uid}`, name);
3537
- }
3538
- function ensureChannelDir(sockPath) {
3539
- const dir = join16(sockPath, "..");
3540
- try {
3541
- mkdirSync13(dir, { recursive: true, mode: 448 });
3542
- const st = statSync3(dir);
3543
- if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
3544
- chmodSync3(dir, 448);
3545
- return true;
3546
- } catch {
3547
- return false;
3548
- }
3549
- }
3550
-
3551
- // src/daemon/window.ts
3552
- function windowForSession(sessionId) {
3553
- const id = readBySession(sessionId);
3554
- if (!id) return null;
3555
- const rec = readWindow(id);
3556
- if (!rec || !isAlive(rec.pid)) return null;
3557
- return id;
3558
- }
3559
-
3560
- // src/core/wake.ts
3561
- import { execFileSync as execFileSync7 } from "child_process";
3562
- import { createHash as createHash3, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
3563
- import { chmodSync as chmodSync4, closeSync, mkdirSync as mkdirSync14, openSync, readdirSync as readdirSync3, readFileSync as readFileSync13, readSync, renameSync as renameSync11, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync13 } from "fs";
3564
- import { tmpdir as tmpdir2 } from "os";
3565
- import { join as join17 } from "path";
3566
- function wakeDir(home = deskbuddyHome()) {
3567
- return join17(home, "wake");
3568
- }
3569
- function marksDir(home = deskbuddyHome()) {
3570
- return join17(wakeDir(home), "marks");
3571
- }
3572
- function safeSid2(sessionId) {
3573
- return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
3574
- }
3575
- function homeTag(home) {
3576
- return createHash3("sha1").update(home).digest("hex").slice(0, 16);
3577
- }
3578
- function wakeChannelPath(platform = process.platform, home = deskbuddyHome()) {
3579
- if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-wake-${homeTag(home)}`;
3580
- const primary = join17(wakeDir(home), "wake.sock");
3581
- if (Buffer.byteLength(primary) <= 103) return primary;
3582
- const uid = typeof process.getuid === "function" ? process.getuid() : 0;
3583
- return join17(tmpdir2(), `deskbuddy-${uid}`, `wake-${homeTag(home)}.sock`);
3584
- }
3585
- function pidExists(pid) {
3586
- if (!Number.isInteger(pid) || pid <= 0) return false;
3587
- try {
3588
- process.kill(pid, 0);
3589
- return true;
3590
- } catch {
3591
- return false;
3592
- }
3593
- }
3594
- function writeWakeMark(sessionId, agentPid, now = Date.now()) {
3595
- const safe = safeSid2(sessionId);
3596
- if (!safe) return;
3597
- try {
3598
- mkdirSync14(marksDir(), { recursive: true, mode: 448 });
3599
- const file = join17(marksDir(), `${safe}.json`);
3600
- const tmp = `${file}.${process.pid}.tmp`;
3601
- writeFileSync13(tmp, JSON.stringify({ agentPid: agentPid ?? 0, daemonPid: process.pid, at: now }), { encoding: "utf8", mode: 384 });
3602
- renameSync11(tmp, file);
3603
- } catch {
3604
- }
3605
- }
3606
- function removeWakeMark(sessionId) {
3607
- const safe = safeSid2(sessionId);
3608
- if (!safe) return;
3609
- try {
3610
- rmSync7(join17(marksDir(), `${safe}.json`), { force: true });
3611
- } catch {
3612
- }
3613
- }
3614
- function clearWakeMarks() {
3615
- let names = [];
3616
- try {
3617
- names = readdirSync3(marksDir());
3618
- } catch {
3619
- return;
3620
- }
3621
- for (const n of names) {
3622
- try {
3623
- rmSync7(join17(marksDir(), n), { force: true });
3624
- } catch {
3625
- }
3626
- }
3627
- }
3628
- function isWakeMarked(sessionId, alive = pidExists) {
3629
- const safe = safeSid2(sessionId);
3630
- if (!safe) return false;
3631
- try {
3632
- const m = JSON.parse(readFileSync13(join17(marksDir(), `${safe}.json`), "utf8"));
3633
- const daemonPid = Number(m?.daemonPid) || 0;
3634
- if (!daemonPid || !alive(daemonPid)) return false;
3635
- const pid = Number(m?.agentPid) || 0;
3636
- return pid > 0 ? alive(pid) : true;
3637
- } catch {
3638
- return false;
3639
- }
3640
- }
3641
- function withWake(states, alive) {
3642
- return states.map((s) => s.remoteWindow === true || !isWakeMarked(s.sessionId, alive) ? s : { ...s, remoteWindow: true });
3643
- }
3644
- function wakeSecretPath(home = deskbuddyHome()) {
3645
- return join17(wakeDir(home), "secret");
3646
- }
3647
- function writeWakeSecret(hex, platform = process.platform) {
3648
- const file = wakeSecretPath();
3649
- mkdirSync14(wakeDir(), { recursive: true, mode: 448 });
3650
- const tmp = `${file}.${process.pid}.tmp`;
3651
- writeFileSync13(tmp, hex, { encoding: "utf8", mode: 384 });
3652
- if (platform === "win32") {
3653
- try {
3654
- execFileSync7("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
3655
- } catch {
3656
- renameSync11(tmp, file);
3657
- return "\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u6587\u4EF6\u7684\u6743\u9650\u6CA1\u80FD\u6536\u7D27\uFF08icacls \u5931\u8D25\uFF09";
3658
- }
3659
- } else {
3660
- chmodSync4(tmp, 384);
3661
- }
3662
- renameSync11(tmp, file);
3663
- return "";
3664
- }
3665
- function currentUserSid() {
3666
- try {
3667
- const out = execFileSync7("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
3668
- const m = /"(S-1-[0-9-]+)"/.exec(out);
3669
- if (m) return `*${m[1]}`;
3670
- } catch {
3671
- }
3672
- return String(process.env.USERNAME);
3673
- }
3674
- function canonicalJson(v) {
3675
- if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
3676
- if (v && typeof v === "object") {
3677
- const o = v;
3678
- return "{" + Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => JSON.stringify(k) + ":" + canonicalJson(o[k])).join(",") + "}";
3679
- }
3680
- return JSON.stringify(v === void 0 ? null : v);
3681
- }
3682
- function wakeMac(secret, ...parts) {
3683
- return createHmac2("sha256", secret).update(parts.join("|"), "utf8").digest("hex");
3684
- }
3685
- var MAC_RE = /^[0-9a-f]{64}$/;
3686
- function macEqual(a, b) {
3687
- if (typeof a !== "string" || typeof b !== "string" || !MAC_RE.test(a) || !MAC_RE.test(b)) return false;
3688
- return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
3689
- }
3690
- var WAKE_MARK_RE = /\[DeskBuddy#([0-9A-Za-z_-]{1,64})(?::[0-9a-f]{32})?\]/;
3691
- var INTERRUPT_RE = /^\[Request interrupted by user/;
3692
- var TRANSCRIPT_TAIL = 256 * 1024;
3693
- function userText(content) {
3694
- if (typeof content === "string") return content;
3695
- if (!Array.isArray(content)) return "";
3696
- return content.filter((x) => x && x.type === "text").map((x) => String(x.text ?? "")).join("\n");
3697
- }
3698
- function analyzeTranscript(file) {
3699
- if (!file) return null;
3700
- let fd;
3701
- try {
3702
- const size = statSync4(file).size;
3703
- const len = Math.min(size, TRANSCRIPT_TAIL);
3704
- const buf = Buffer.alloc(len);
3705
- fd = openSync(file, "r");
3706
- readSync(fd, buf, 0, len, size - len);
3707
- const lines2 = buf.toString("utf8").split("\n");
3708
- let seg = false;
3709
- let reply = null;
3710
- let humanAfter = false;
3711
- let interrupted = null;
3712
- for (let i = lines2.length - 1; i >= 0; i--) {
3713
- const line = lines2[i];
3714
- if (line.indexOf('"assistant"') === -1 && line.indexOf('"user"') === -1) continue;
3715
- let o;
3716
- try {
3717
- o = JSON.parse(line);
3718
- } catch {
3719
- continue;
3720
- }
3721
- if (!o || !o.message) continue;
3722
- if (o.type === "assistant") {
3723
- const t = userText(o.message.content).trim();
3724
- if (t) {
3725
- seg = true;
3726
- if (reply === null) reply = t;
3727
- }
3728
- continue;
3729
- }
3730
- if (o.type !== "user") continue;
3731
- const text = userText(o.message.content);
3732
- if (!text) continue;
3733
- const m = WAKE_MARK_RE.exec(text);
3734
- if (!m && (o.isMeta === true || text.trim().startsWith("<"))) continue;
3735
- if (m) return { id: m[1], answered: seg, humanAfter, interrupted: interrupted === true, reply: reply ?? "" };
3736
- if (interrupted === null) interrupted = INTERRUPT_RE.test(text.trim());
3737
- humanAfter = true;
3738
- seg = false;
3739
- reply = null;
3740
- }
3741
- return interrupted === null ? null : { id: "", answered: false, humanAfter, interrupted, reply: "" };
3996
+ }
3997
+ }
3998
+ function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
3999
+ if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
4000
+ const name = `${windowId.slice(0, 16)}.sock`;
4001
+ const primary = join18(home, "win", name);
4002
+ if (Buffer.byteLength(primary) <= 103) return primary;
4003
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
4004
+ return join18(tmpdir2(), `deskbuddy-${uid}`, name);
4005
+ }
4006
+ function ensureChannelDir(sockPath) {
4007
+ const dir = join18(sockPath, "..");
4008
+ try {
4009
+ mkdirSync15(dir, { recursive: true, mode: 448 });
4010
+ const st = statSync5(dir);
4011
+ if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
4012
+ chmodSync4(dir, 448);
4013
+ return true;
4014
+ } catch {
4015
+ return false;
4016
+ }
4017
+ }
4018
+
4019
+ // src/daemon/window.ts
4020
+ function windowForSession(sessionId) {
4021
+ const id = readBySession(sessionId);
4022
+ if (!id) return null;
4023
+ const rec = readWindow(id);
4024
+ if (!rec || !isAlive(rec.pid)) return null;
4025
+ return id;
4026
+ }
4027
+
4028
+ // src/core/launch.ts
4029
+ import { mkdirSync as mkdirSync16, readFileSync as readFileSync15, renameSync as renameSync13, rmSync as rmSync9, writeFileSync as writeFileSync15 } from "fs";
4030
+ import { join as join19 } from "path";
4031
+ var RUN_ID_RE = /^[a-f0-9]{32}$/;
4032
+ var SESSION_UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
4033
+ function launchDir(home = deskbuddyHome()) {
4034
+ return join19(home, "launch");
4035
+ }
4036
+ function planFile(runId) {
4037
+ return join19(launchDir(), `${runId}.json`);
4038
+ }
4039
+ function writeLaunch(runId, plan) {
4040
+ if (!RUN_ID_RE.test(runId)) throw new Error("bad runId");
4041
+ if (plan.resume && !SESSION_UUID_RE.test(plan.resume)) throw new Error("bad session id");
4042
+ mkdirSync16(launchDir(), { recursive: true, mode: 448 });
4043
+ const tmp = `${planFile(runId)}.${process.pid}.tmp`;
4044
+ writeFileSync15(tmp, JSON.stringify(plan), { encoding: "utf8", mode: 384 });
4045
+ restrictToUser(tmp);
4046
+ renameSync13(tmp, planFile(runId));
4047
+ }
4048
+ function claimLaunch(runId) {
4049
+ if (!RUN_ID_RE.test(runId)) return null;
4050
+ const claimed = join19(launchDir(), `${runId}.${process.pid}.claimed`);
4051
+ try {
4052
+ renameSync13(planFile(runId), claimed);
4053
+ } catch {
4054
+ return null;
4055
+ }
4056
+ try {
4057
+ const p = JSON.parse(readFileSync15(claimed, "utf8"));
4058
+ if (!p || typeof p.cwd !== "string" || typeof p.resume !== "string") return null;
4059
+ if (p.resume && !SESSION_UUID_RE.test(p.resume)) return null;
4060
+ return p;
3742
4061
  } catch {
3743
4062
  return null;
3744
4063
  } finally {
3745
- if (fd !== void 0) {
3746
- try {
3747
- closeSync(fd);
3748
- } catch {
3749
- }
4064
+ try {
4065
+ rmSync9(claimed, { force: true });
4066
+ } catch {
4067
+ }
4068
+ }
4069
+ }
4070
+ function launchPending(runId) {
4071
+ try {
4072
+ readFileSync15(planFile(runId));
4073
+ return true;
4074
+ } catch {
4075
+ return false;
4076
+ }
4077
+ }
4078
+
4079
+ // src/daemon/openWindow.ts
4080
+ import { execFile as execFile3, execFileSync as execFileSync8, spawn as spawn2 } from "child_process";
4081
+ import { chmodSync as chmodSync5, mkdirSync as mkdirSync17, rmSync as rmSync10, writeFileSync as writeFileSync16 } from "fs";
4082
+ import { userInfo } from "os";
4083
+ import { join as join20 } from "path";
4084
+ function safePath(p) {
4085
+ return /^(?:[A-Za-z]:[\\/]|\/)/.test(p) && !/[\r\n\0"'`$%;]/.test(p);
4086
+ }
4087
+ function checkTarget(t) {
4088
+ if (!RUN_ID_RE.test(t.runId)) throw new Error("bad runId");
4089
+ 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");
4090
+ }
4091
+ var shq = (s) => `'${s}'`;
4092
+ function macCommandScript(t) {
4093
+ checkTarget(t);
4094
+ return `#!/bin/sh
4095
+ exec ${shq(t.node)} ${shq(t.cli)} wrap --launch ${t.runId} --home ${shq(t.home)}
4096
+ `;
4097
+ }
4098
+ function winWtArgs(t) {
4099
+ checkTarget(t);
4100
+ return ["-w", "new", t.node, t.cli, "wrap", "--launch", t.runId, "--home", t.home];
4101
+ }
4102
+ var psq = (s) => `'${s.replace(/'/g, "''")}'`;
4103
+ function winStartProcessScript(t) {
4104
+ checkTarget(t);
4105
+ const arg = (s) => psq(/\s/.test(s) ? `"${s}"` : s);
4106
+ return `Start-Process -FilePath ${psq(t.node)} -ArgumentList @(${[t.cli, "wrap", "--launch", t.runId, "--home", t.home].map(arg).join(",")})`;
4107
+ }
4108
+ function encodePs(script) {
4109
+ return Buffer.from(script, "utf16le").toString("base64");
4110
+ }
4111
+ function guiAvailable(d) {
4112
+ if (d.env.SSH_CONNECTION || d.env.SSH_TTY) return false;
4113
+ if (d.platform === "darwin") {
4114
+ const m = d.macManager().trim();
4115
+ if (m === "Aqua") return true;
4116
+ if (m !== "Background") return false;
4117
+ const me = d.userName?.() ?? "";
4118
+ return !!me && (d.macConsoleUser?.() ?? "").trim() === me;
4119
+ }
4120
+ if (d.platform === "win32") {
4121
+ const s = d.winSessionId();
4122
+ return s !== null && s > 0;
4123
+ }
4124
+ return false;
4125
+ }
4126
+ var defaultGuiDeps = () => ({
4127
+ platform: process.platform,
4128
+ env: process.env,
4129
+ macManager: () => {
4130
+ try {
4131
+ return execFileSync8("launchctl", ["managername"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
4132
+ } catch {
4133
+ return "";
4134
+ }
4135
+ },
4136
+ macConsoleUser: () => {
4137
+ try {
4138
+ return execFileSync8("stat", ["-f", "%Su", "/dev/console"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
4139
+ } catch {
4140
+ return "";
3750
4141
  }
4142
+ },
4143
+ userName: () => {
4144
+ try {
4145
+ return userInfo().username;
4146
+ } catch {
4147
+ return "";
4148
+ }
4149
+ },
4150
+ winSessionId: () => {
4151
+ try {
4152
+ const out = execFileSync8("tasklist", ["/FI", `PID eq ${process.pid}`, "/FO", "CSV", "/NH"], {
4153
+ encoding: "utf8",
4154
+ stdio: ["ignore", "pipe", "ignore"],
4155
+ windowsHide: true,
4156
+ timeout: 5e3
4157
+ });
4158
+ const cols = out.trim().split("\n")[0]?.match(/"([^"]*)"/g)?.map((c) => c.slice(1, -1)) ?? [];
4159
+ const n = Number(cols[3]);
4160
+ return Number.isInteger(n) ? n : null;
4161
+ } catch {
4162
+ return null;
4163
+ }
4164
+ }
4165
+ });
4166
+ var defaultOpenerDeps = (findWt2) => ({
4167
+ platform: process.platform,
4168
+ findWt: findWt2,
4169
+ run: (file, args) => new Promise((resolve5, reject) => {
4170
+ execFile3(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) : resolve5());
4171
+ }),
4172
+ spawnDetached: (file, args) => {
4173
+ const c = spawn2(file, args, { detached: true, stdio: "ignore", shell: false, windowsHide: false });
4174
+ c.on("error", () => {
4175
+ });
4176
+ c.unref();
4177
+ }
4178
+ });
4179
+ async function openTerminal(t, d) {
4180
+ checkTarget(t);
4181
+ if (d.platform === "darwin") {
4182
+ mkdirSync17(launchDir(t.home), { recursive: true, mode: 448 });
4183
+ const file = join20(launchDir(t.home), `${t.runId}.command`);
4184
+ writeFileSync16(file, macCommandScript(t), { encoding: "utf8", mode: 448 });
4185
+ chmodSync5(file, 448);
4186
+ await d.run("open", ["-a", "Terminal", file]);
4187
+ return "terminal";
4188
+ }
4189
+ if (d.platform === "win32") {
4190
+ const wt = d.findWt();
4191
+ if (wt) {
4192
+ d.spawnDetached(wt, winWtArgs(t));
4193
+ return "wt";
4194
+ }
4195
+ await d.run("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", encodePs(winStartProcessScript(t))]);
4196
+ return "powershell";
4197
+ }
4198
+ throw new Error(`\u6682\u4E0D\u652F\u6301\u5728 ${d.platform} \u4E0A\u5F00\u7A97`);
4199
+ }
4200
+ function removeCommandFile(home, runId) {
4201
+ if (!RUN_ID_RE.test(runId)) return;
4202
+ try {
4203
+ rmSync10(join20(launchDir(home), `${runId}.command`), { force: true });
4204
+ } catch {
3751
4205
  }
3752
4206
  }
3753
4207
 
3754
4208
  // src/daemon/wake.ts
3755
- import { randomBytes as randomBytes5 } from "crypto";
3756
- import { rmSync as rmSync8 } from "fs";
4209
+ import { randomBytes as randomBytes6 } from "crypto";
4210
+ import { rmSync as rmSync11 } from "fs";
3757
4211
  import { createServer as createServer3 } from "net";
3758
4212
  var BUSY_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
3759
4213
  var STOP_MIN_MS = 1500;
@@ -4040,7 +4494,7 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
4040
4494
  if (platform !== "win32") {
4041
4495
  if (!ensureChannelDir(path)) return Promise.resolve(null);
4042
4496
  try {
4043
- rmSync8(path, { force: true });
4497
+ rmSync11(path, { force: true });
4044
4498
  } catch {
4045
4499
  }
4046
4500
  }
@@ -4092,7 +4546,7 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
4092
4546
  return;
4093
4547
  }
4094
4548
  nonceW = m.nonceW;
4095
- nonceD = randomBytes5(16).toString("hex");
4549
+ nonceD = randomBytes6(16).toString("hex");
4096
4550
  phase = "auth2";
4097
4551
  raw({ t: "auth", nonceD, mac: wakeMac(secret, "d", nonceW, nonceD) });
4098
4552
  return;
@@ -4147,6 +4601,10 @@ var POLICY_VIOLATION = 1008;
4147
4601
  var PING_FRAME_SERVICE = "ping";
4148
4602
  var PONG_FRAME = JSON.stringify({ service: "pong", version: "1.0" });
4149
4603
  var CODEX_REMOTE_REFRESH_MS = 30 * 1e3;
4604
+ var CLAIM_WAIT_MS = 15 * 1e3;
4605
+ var FIRST_LINE_WAIT_MS = 10 * 60 * 1e3;
4606
+ var FIRST_LINE_CAP_MS = 18 * 60 * 1e3;
4607
+ var WINDOW_READY_MS = 30 * 1e3;
4150
4608
  var CODEX_WATCH_MS = 5 * 1e3;
4151
4609
  function defaultCodexAppServers() {
4152
4610
  const cfg = loadConfig();
@@ -4176,6 +4634,11 @@ var Daemon = class {
4176
4634
  #aliveReport = null;
4177
4635
  #codexTimer = null;
4178
4636
  #wake;
4637
+ /**
4638
+ * 会话号 → 这个会话正在进行的开窗流程(开窗、等认领、交首句;降级后台时一直到后台那次跑完)。
4639
+ * 🔴 同一会话不能被两个进程同时续跑(实测会静默丢一整轮):后到的指令等它结束再路由
4640
+ */
4641
+ #opening = /* @__PURE__ */ new Map();
4179
4642
  #wakeServer = null;
4180
4643
  #wakeTimer = null;
4181
4644
  #wakeReport = null;
@@ -4199,7 +4662,16 @@ var Daemon = class {
4199
4662
  codexRoute: deps.codexRoute ?? ((tid) => this.#codexRouteFor(tid)),
4200
4663
  report: deps.report ?? ((p) => api.commandResult(p)),
4201
4664
  windowFor: deps.windowFor ?? windowForSession,
4202
- sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), req2, onReply)),
4665
+ sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), windowSecret(id), req2, onReply)),
4666
+ gui: deps.gui ?? (() => guiAvailable(defaultGuiDeps())),
4667
+ openWindow: deps.openWindow ?? ((runId) => this.#openTerminal(runId)),
4668
+ endedCwd: deps.endedCwd ?? ((sid, tool, stateCwd) => resolveEndedCwd(sid, tool, {
4669
+ readEnded: (s) => readEnded(s) ?? (stateCwd ? { sessionId: s, tool, cwd: stateCwd, interactive: false, endedAt: 0 } : null),
4670
+ transcriptCwd: (s) => claudeTranscriptCwd(s),
4671
+ isDir: isDirSync
4672
+ })),
4673
+ claimWaitMs: deps.claimWaitMs ?? CLAIM_WAIT_MS,
4674
+ windowReadyMs: deps.windowReadyMs ?? WINDOW_READY_MS,
4203
4675
  wakePath: deps.wakePath === void 0 ? wakeChannelPath() : deps.wakePath,
4204
4676
  onWindowDone: deps.onWindowDone ?? (() => {
4205
4677
  })
@@ -4253,7 +4725,7 @@ var Daemon = class {
4253
4725
  const path = this.#deps.wakePath;
4254
4726
  if (!path) return;
4255
4727
  clearWakeMarks();
4256
- const secret = randomBytes6(32);
4728
+ const secret = randomBytes7(32);
4257
4729
  let warn = "";
4258
4730
  try {
4259
4731
  warn = writeWakeSecret(secret.toString("hex"));
@@ -4515,6 +4987,23 @@ var Daemon = class {
4515
4987
  return;
4516
4988
  }
4517
4989
  this.#seen.save();
4990
+ cmd.receivedAt = this.#deps.now();
4991
+ this.#route(cmd);
4992
+ }
4993
+ /**
4994
+ * 指令分派(去重之后)。同一会话的开窗流程还没走完时,后到的指令等它结束再从头路由一遍
4995
+ * (届时窗口已登记 → 走窗口写入;降级后台了 → 按原来的按会话串行排队)
4996
+ */
4997
+ #route(cmd) {
4998
+ if (cmd.tool === "claude_code" && cmd.sessionId) {
4999
+ const sid = resolveAlias(cmd.sessionId);
5000
+ const pending = this.#opening.get(sid);
5001
+ if (pending) {
5002
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u7B49\u540C\u4E00\u4F1A\u8BDD\u7684\u5F00\u7A97\u6D41\u7A0B\u7ED3\u675F\u540E\u518D\u5206\u6D3E`);
5003
+ void pending.then(() => this.#route(cmd));
5004
+ return;
5005
+ }
5006
+ }
4518
5007
  if (cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
4519
5008
  const sid = resolveAlias(cmd.sessionId);
4520
5009
  let win = null;
@@ -4556,8 +5045,200 @@ var Daemon = class {
4556
5045
  const sid = resolveAlias(cmd.sessionId);
4557
5046
  if (this.#wake.submit(cmd, sid)) return;
4558
5047
  }
5048
+ if (cmd.tool === "claude_code" && !isExpired(cmd, this.#deps.now())) {
5049
+ void this.#openOrDispatch(cmd).catch((e) => {
5050
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u5F00\u7A97\u6D41\u7A0B\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
5051
+ this.#report(cmd, cmd.sessionId, "failed", "", "window_failed");
5052
+ });
5053
+ return;
5054
+ }
5055
+ if (cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
5056
+ const sid = resolveAlias(cmd.sessionId);
5057
+ if (!readState(sid) && readEnded(sid)?.tool === cmd.tool) {
5058
+ const r = this.#deps.endedCwd(sid, cmd.tool, "");
5059
+ if (!r.ok) {
5060
+ 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`);
5061
+ this.#report(cmd, sid, "rejected", "", r.reason);
5062
+ return;
5063
+ }
5064
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\uFF0C\u540E\u53F0\u7EED\u8DD1`);
5065
+ void this.#headless(cmd, sid, r.cwd);
5066
+ return;
5067
+ }
5068
+ }
4559
5069
  this.#dispatch(cmd);
4560
5070
  }
5071
+ /**
5072
+ * 新建任务、会话已结束 → 开窗;会话还活着(后台 -p 在跑、或在用户自己开的窗口里)→ 原来的路径(排队 / window_open)
5073
+ */
5074
+ async #openOrDispatch(cmd) {
5075
+ if (tooLongToInject(cmd.text)) {
5076
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u592A\u957F\uFF08${cmd.text.length} \u5B57\uFF09\uFF0C\u4E0D\u6267\u884C`);
5077
+ this.#report(cmd, cmd.sessionId ? resolveAlias(cmd.sessionId) : "", "rejected", "", TEXT_TOO_LONG);
5078
+ return;
5079
+ }
5080
+ if (!cmd.sessionId) {
5081
+ let cwd = "";
5082
+ if (cmd.dirRef) {
5083
+ cwd = dirOf(cmd.dirRef);
5084
+ if (!cwd) {
5085
+ this.#report(cmd, "", "rejected", "", "dir_gone");
5086
+ return;
5087
+ }
5088
+ } else {
5089
+ cwd = loadConfig().remoteCwd || "";
5090
+ if (!cwd) {
5091
+ this.#report(cmd, "", "rejected", "", "no_remote_cwd");
5092
+ return;
5093
+ }
5094
+ }
5095
+ await this.#openWindowFor(cmd, "", cwd);
5096
+ return;
5097
+ }
5098
+ const sid = resolveAlias(cmd.sessionId);
5099
+ for (let p = this.#opening.get(sid); p; p = this.#opening.get(sid)) await p;
5100
+ let release2;
5101
+ this.#opening.set(sid, new Promise((r) => release2 = r));
5102
+ try {
5103
+ if (this.#queue.busy(sid)) await this.#queue.enqueue(sid, async () => {
5104
+ });
5105
+ const st = readState(sid);
5106
+ if (st && (!st.pid || isAlive(st.pid, st.pidName))) {
5107
+ this.#dispatch(cmd);
5108
+ return;
5109
+ }
5110
+ if (!SESSION_UUID_RE.test(sid)) {
5111
+ this.#dispatch(cmd);
5112
+ return;
5113
+ }
5114
+ const r = this.#deps.endedCwd(sid, cmd.tool, st?.cwd ?? "");
5115
+ if (!r.ok) {
5116
+ 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`);
5117
+ this.#report(cmd, sid, "rejected", "", r.reason);
5118
+ return;
5119
+ }
5120
+ await this.#openWindowFor(cmd, sid, r.cwd);
5121
+ } finally {
5122
+ this.#opening.delete(sid);
5123
+ release2();
5124
+ }
5125
+ }
5126
+ /**
5127
+ * 开窗 → 等认领 → 首句经通道交给窗口;任何一步起不来 → 后台 -p(回报 started/headless)。
5128
+ * 返回时:窗口已收下首句 / 已按失败回报 / 降级的后台那次已跑完
5129
+ */
5130
+ async #openWindowFor(cmd, sid, cwd) {
5131
+ const headless = (why, c = cmd) => {
5132
+ log(`\u6307\u4EE4 ${cmd.instructionId} ${why}\uFF0C\u6539\u540E\u53F0\u6267\u884C`);
5133
+ return this.#headless(c, sid, cwd);
5134
+ };
5135
+ let gui = false;
5136
+ try {
5137
+ gui = this.#deps.gui();
5138
+ } catch {
5139
+ gui = false;
5140
+ }
5141
+ if (!gui) return headless("\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\uFF08SSH / \u9501\u5B9A\u7684\u670D\u52A1\u4F1A\u8BDD / Linux\uFF09");
5142
+ const runId = randomBytes7(16).toString("hex");
5143
+ try {
5144
+ writeLaunch(runId, { tool: "claude_code", cwd, resume: sid, at: this.#deps.now() });
5145
+ } catch (e) {
5146
+ return headless(`\u5199\u4EA4\u63A5\u5355\u5931\u8D25\uFF1A${e.message}`);
5147
+ }
5148
+ const openedAt = this.#deps.now();
5149
+ try {
5150
+ await this.#deps.openWindow(runId);
5151
+ } catch (e) {
5152
+ if (claimLaunch(runId)) {
5153
+ removeCommandFile(deskbuddyHome(), runId);
5154
+ return headless(`\u5F00\u7A97\u5931\u8D25\uFF1A${e.message}`);
5155
+ }
5156
+ 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`);
5157
+ }
5158
+ const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
5159
+ const claimDeadline = this.#deps.now() + this.#deps.claimWaitMs;
5160
+ while (launchPending(runId) && this.#deps.now() < claimDeadline) await sleep2(300);
5161
+ removeCommandFile(deskbuddyHome(), runId);
5162
+ if (launchPending(runId) && claimLaunch(runId)) return headless(`${Math.round(this.#deps.claimWaitMs / 1e3)} \u79D2\u5185\u7A97\u53E3\u6CA1\u8D77\u6765`);
5163
+ 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`);
5164
+ const firstLineExpire = Math.min(Math.max(cmd.expireAt, openedAt + FIRST_LINE_WAIT_MS), (cmd.receivedAt ?? openedAt) + FIRST_LINE_CAP_MS);
5165
+ const wcmd = cmd.expireAt > 0 ? { ...cmd, expireAt: Math.max(cmd.expireAt, firstLineExpire) } : cmd;
5166
+ const req2 = { instructionId: cmd.instructionId, sessionId: "", text: cmd.text, expireAt: wcmd.expireAt };
5167
+ const fb = { p: null, final: false };
5168
+ const readyDeadline = this.#deps.now() + this.#deps.windowReadyMs;
5169
+ while (this.#deps.now() < readyDeadline) {
5170
+ let r = "fallback";
5171
+ try {
5172
+ r = await this.#deps.sendToWindow(runId, req2, (reply) => {
5173
+ if (reply.status === "ack") return;
5174
+ if (reply.status !== "queued") fb.final = true;
5175
+ if (reply.status === "not_here") {
5176
+ if (!fb.p) fb.p = headless("\u9996\u53E5\u5199\u5165\u524D\u7A97\u53E3\u5DF2\u5173\u95ED");
5177
+ return;
5178
+ }
5179
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u7A97\u53E3 ${reply.status}${reply.reason ? " " + reply.reason : ""}`);
5180
+ this.#report(cmd, sid, reply.status, "window", reply.reason ?? "");
5181
+ });
5182
+ } catch {
5183
+ r = "fallback";
5184
+ }
5185
+ if (r === "handled") {
5186
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u9996\u53E5\u5DF2\u4EA4\u7ED9\u65B0\u7A97\u53E3 len=${cmd.text.length}`);
5187
+ if (sid) {
5188
+ const left = wcmd.expireAt > 0 ? Math.max(wcmd.expireAt - this.#deps.now(), 0) : FIRST_LINE_WAIT_MS;
5189
+ const cap = this.#deps.now() + left + this.#deps.windowReadyMs;
5190
+ while (!fb.final && this.#deps.now() < cap) {
5191
+ let found = null;
5192
+ try {
5193
+ found = this.#deps.windowFor(sid);
5194
+ } catch {
5195
+ found = null;
5196
+ }
5197
+ if (found) break;
5198
+ await sleep2(200);
5199
+ }
5200
+ }
5201
+ await sleep2(50);
5202
+ if (fb.p) await fb.p;
5203
+ return;
5204
+ }
5205
+ await sleep2(1e3);
5206
+ }
5207
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u65B0\u7A97\u53E3\u7684\u901A\u9053\u4E00\u76F4\u6CA1\u5C31\u7EEA`);
5208
+ this.#report(cmd, sid, "failed", "window", "window_failed");
5209
+ }
5210
+ /**
5211
+ * 后台 -p:新建任务走原来的路径;续跑已结束会话用解析出的 cwd,不再查状态文件(它已被删)。
5212
+ * 返回的 promise 在后台那次跑完时落定(开窗流程的会话锁要一直占到那时)
5213
+ */
5214
+ #headless(cmd, sid, cwd) {
5215
+ if (isExpired(cmd, this.#deps.now())) {
5216
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u5DF2\u8FC7\u671F\uFF0C\u4E0D\u518D\u540E\u53F0\u6267\u884C`);
5217
+ this.#report(cmd, sid, "rejected", "", "expired");
5218
+ return Promise.resolve();
5219
+ }
5220
+ if (!sid) {
5221
+ this.#dispatch(cmd);
5222
+ return Promise.resolve();
5223
+ }
5224
+ return this.#queue.enqueue(
5225
+ sid,
5226
+ () => this.#execute(cmd, sid, cwd, cmd.tool, true),
5227
+ (e) => {
5228
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u65F6\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
5229
+ this.#report(cmd, sid, "failed", "", "exec_failed");
5230
+ }
5231
+ );
5232
+ }
5233
+ /** 真的弹窗口(测试环境绝不弹:vitest 下直接当开窗失败) */
5234
+ async #openTerminal(runId) {
5235
+ if (process.env.VITEST) throw new Error("\u6D4B\u8BD5\u73AF\u5883\u4E0D\u5F00\u7A97");
5236
+ const how = await openTerminal(
5237
+ { node: this.#deps.nodePath, cli: defaultCliPath(), home: deskbuddyHome(), runId },
5238
+ defaultOpenerDeps(() => findWt())
5239
+ );
5240
+ log(`\u5F00\u7A97 ${runId.slice(0, 8)} \u65B9\u5F0F=${how}`);
5241
+ }
4561
5242
  /**
4562
5243
  * 经 app-server 往 Codex 会话里发一轮(切片 B)。回报语义与 Claude 窗口一致(mode=window):
4563
5244
  * 排队 queued → 发出 started;排队中过期 rejected/expired。
@@ -4685,8 +5366,8 @@ var Daemon = class {
4685
5366
  }
4686
5367
  );
4687
5368
  }
4688
- async #execute(cmd, sessionId, cwd, handleKey = cmd.tool) {
4689
- if (sessionId) {
5369
+ async #execute(cmd, sessionId, cwd, handleKey = cmd.tool, ended = false) {
5370
+ if (sessionId && !ended) {
4690
5371
  const fresh = readState(sessionId);
4691
5372
  if (!fresh) {
4692
5373
  log(`\u6307\u4EE4 ${cmd.instructionId} \u653E\u5F03\uFF1A\u4F1A\u8BDD ${sessionId} \u5728\u6267\u884C\u524D\u5DF2\u6D88\u5931`);
@@ -4810,7 +5491,7 @@ var Daemon = class {
4810
5491
  }
4811
5492
  };
4812
5493
  function newRunId() {
4813
- return randomBytes6(12).toString("hex");
5494
+ return randomBytes7(12).toString("hex");
4814
5495
  }
4815
5496
  async function runDaemon(deps = {}) {
4816
5497
  const d = new Daemon(deps);
@@ -4828,7 +5509,7 @@ async function runDaemonCommand(opts = {}) {
4828
5509
  }
4829
5510
  if (opts.foreground) process.env.DESKBUDDY_DAEMON_FOREGROUND = "1";
4830
5511
  try {
4831
- writeFileSync14(pidFilePath(), String(process.pid), "utf8");
5512
+ writeFileSync17(pidFilePath(), String(process.pid), "utf8");
4832
5513
  } catch {
4833
5514
  }
4834
5515
  let daemon;
@@ -4844,7 +5525,7 @@ async function runDaemonCommand(opts = {}) {
4844
5525
  log(`\u6536\u5230 ${sig}\uFF0C\u9000\u51FA`);
4845
5526
  daemon.stop();
4846
5527
  try {
4847
- rmSync9(pidFilePath(), { force: true });
5528
+ rmSync12(pidFilePath(), { force: true });
4848
5529
  } catch {
4849
5530
  }
4850
5531
  process.exit(0);
@@ -4861,8 +5542,8 @@ async function runDaemonCommand(opts = {}) {
4861
5542
  }
4862
5543
 
4863
5544
  // src/core/registry.ts
4864
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync15 } from "fs";
4865
- import { join as join18 } from "path";
5545
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync16, writeFileSync as writeFileSync18 } from "fs";
5546
+ import { join as join21 } from "path";
4866
5547
  var FALLBACK = {
4867
5548
  version: "builtin",
4868
5549
  tools: {
@@ -4940,11 +5621,11 @@ var FALLBACK = {
4940
5621
  }
4941
5622
  };
4942
5623
  function registryCachePath() {
4943
- return join18(deskbuddyHome(), "tool-registry.json");
5624
+ return join21(deskbuddyHome(), "tool-registry.json");
4944
5625
  }
4945
5626
  function loadRegistry() {
4946
5627
  try {
4947
- return JSON.parse(readFileSync14(registryCachePath(), "utf8"));
5628
+ return JSON.parse(readFileSync16(registryCachePath(), "utf8"));
4948
5629
  } catch {
4949
5630
  return FALLBACK;
4950
5631
  }
@@ -4952,15 +5633,15 @@ function loadRegistry() {
4952
5633
  async function refreshRegistry() {
4953
5634
  try {
4954
5635
  const registry = await api.toolRegistry();
4955
- mkdirSync15(deskbuddyHome(), { recursive: true });
4956
- writeFileSync15(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
5636
+ mkdirSync18(deskbuddyHome(), { recursive: true });
5637
+ writeFileSync18(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
4957
5638
  } catch {
4958
5639
  }
4959
5640
  }
4960
5641
 
4961
5642
  // src/adapters/ccHook.ts
4962
- import { chmodSync as chmodSync5, existsSync as existsSync11, mkdirSync as mkdirSync16, readFileSync as readFileSync15, renameSync as renameSync12, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync16 } from "fs";
4963
- import { dirname as dirname6, join as join19 } from "path";
5643
+ import { chmodSync as chmodSync6, existsSync as existsSync11, mkdirSync as mkdirSync19, readFileSync as readFileSync17, renameSync as renameSync14, rmSync as rmSync13, statSync as statSync6, writeFileSync as writeFileSync19 } from "fs";
5644
+ import { dirname as dirname6, join as join22 } from "path";
4964
5645
  var CC_EVENTS = [
4965
5646
  "UserPromptSubmit",
4966
5647
  "PreToolUse",
@@ -4971,7 +5652,7 @@ var CC_EVENTS = [
4971
5652
  ];
4972
5653
  function readSettings(path) {
4973
5654
  if (!existsSync11(path)) return {};
4974
- const raw = readFileSync15(path, "utf8");
5655
+ const raw = readFileSync17(path, "utf8");
4975
5656
  if (raw.trim() === "") return {};
4976
5657
  try {
4977
5658
  return JSON.parse(raw);
@@ -4980,17 +5661,17 @@ function readSettings(path) {
4980
5661
  }
4981
5662
  }
4982
5663
  function writeSettings(path, settings) {
4983
- mkdirSync16(dirname6(path), { recursive: true });
5664
+ mkdirSync19(dirname6(path), { recursive: true });
4984
5665
  let mode;
4985
5666
  try {
4986
- mode = statSync5(path).mode & 511;
5667
+ mode = statSync6(path).mode & 511;
4987
5668
  } catch {
4988
5669
  mode = void 0;
4989
5670
  }
4990
5671
  const tmp = path + ".deskbuddy.tmp";
4991
- writeFileSync16(tmp, JSON.stringify(settings, null, 2), "utf8");
4992
- if (mode !== void 0) chmodSync5(tmp, mode);
4993
- renameSync12(tmp, path);
5672
+ writeFileSync19(tmp, JSON.stringify(settings, null, 2), "utf8");
5673
+ if (mode !== void 0) chmodSync6(tmp, mode);
5674
+ renameSync14(tmp, path);
4994
5675
  }
4995
5676
  function mentions(command, script) {
4996
5677
  return !!command && (command.includes(script) || command.includes(fwdSlash(script)));
@@ -5093,8 +5774,8 @@ function installCcRewake(settingsPath, wakePath, platform = process.platform) {
5093
5774
  function uninstallCcHook(settingsPath, hookPath) {
5094
5775
  const settings = readSettings(settingsPath);
5095
5776
  if (!settings.hooks) return;
5096
- const gatePath = join19(dirname6(hookPath), "gate.cjs");
5097
- const wakePath = join19(dirname6(hookPath), "wake.cjs");
5777
+ const gatePath = join22(dirname6(hookPath), "gate.cjs");
5778
+ const wakePath = join22(dirname6(hookPath), "wake.cjs");
5098
5779
  for (const event of Object.keys(settings.hooks)) {
5099
5780
  const groups = settings.hooks[event] ?? [];
5100
5781
  const kept = [];
@@ -5108,14 +5789,14 @@ function uninstallCcHook(settingsPath, hookPath) {
5108
5789
  }
5109
5790
  writeSettings(settingsPath, settings);
5110
5791
  try {
5111
- rmSync10(join19(dirname6(hookPath), "wake", "tokens"), { recursive: true, force: true });
5792
+ rmSync13(join22(dirname6(hookPath), "wake", "tokens"), { recursive: true, force: true });
5112
5793
  } catch {
5113
5794
  }
5114
5795
  }
5115
5796
 
5116
5797
  // src/adapters/codexHook.ts
5117
- import { chmodSync as chmodSync6, existsSync as existsSync12, mkdirSync as mkdirSync17, readFileSync as readFileSync16, renameSync as renameSync13, statSync as statSync6, writeFileSync as writeFileSync17 } from "fs";
5118
- import { dirname as dirname7, join as join20 } from "path";
5798
+ import { chmodSync as chmodSync7, existsSync as existsSync12, mkdirSync as mkdirSync20, readFileSync as readFileSync18, renameSync as renameSync15, statSync as statSync7, writeFileSync as writeFileSync20 } from "fs";
5799
+ import { dirname as dirname7, join as join23 } from "path";
5119
5800
  var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
5120
5801
  var CODEX_MARKER_END = "# <<< deskbuddy <<<";
5121
5802
  var CODEX_EVENTS = [
@@ -5128,7 +5809,7 @@ var CODEX_EVENTS = [
5128
5809
  ];
5129
5810
  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";
5130
5811
  function codexConfigPath() {
5131
- return join20(codexHome(), "config.toml");
5812
+ return join23(codexHome(), "config.toml");
5132
5813
  }
5133
5814
  function renderBlock(hookPath, events, tool, platform) {
5134
5815
  const command = JSON.stringify(`node ${platform === "win32" ? winPsPath(hookPath) : hookPath} ${tool}`);
@@ -5147,17 +5828,17 @@ function renderBlock(hookPath, events, tool, platform) {
5147
5828
  return lines2.join("\n");
5148
5829
  }
5149
5830
  function writeAtomic(path, content) {
5150
- mkdirSync17(dirname7(path), { recursive: true });
5831
+ mkdirSync20(dirname7(path), { recursive: true });
5151
5832
  let mode;
5152
5833
  try {
5153
- mode = statSync6(path).mode & 511;
5834
+ mode = statSync7(path).mode & 511;
5154
5835
  } catch {
5155
5836
  mode = void 0;
5156
5837
  }
5157
5838
  const tmp = path + ".deskbuddy.tmp";
5158
- writeFileSync17(tmp, content, "utf8");
5159
- if (mode !== void 0) chmodSync6(tmp, mode);
5160
- renameSync13(tmp, path);
5839
+ writeFileSync20(tmp, content, "utf8");
5840
+ if (mode !== void 0) chmodSync7(tmp, mode);
5841
+ renameSync15(tmp, path);
5161
5842
  }
5162
5843
  function assertSafeToAppend(raw, path) {
5163
5844
  for (const quote of ['"""', "'''"]) {
@@ -5167,7 +5848,7 @@ function assertSafeToAppend(raw, path) {
5167
5848
  }
5168
5849
  }
5169
5850
  function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "codex", platform = process.platform) {
5170
- const raw = existsSync12(configPath2) ? readFileSync16(configPath2, "utf8") : "";
5851
+ const raw = existsSync12(configPath2) ? readFileSync18(configPath2, "utf8") : "";
5171
5852
  if (raw.includes(CODEX_MARKER_BEGIN)) return;
5172
5853
  assertSafeToAppend(raw, configPath2);
5173
5854
  const block = renderBlock(hookPath, events, tool, platform);
@@ -5176,7 +5857,7 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
5176
5857
  }
5177
5858
  function uninstallCodexHook(configPath2) {
5178
5859
  if (!existsSync12(configPath2)) return;
5179
- const raw = readFileSync16(configPath2, "utf8");
5860
+ const raw = readFileSync18(configPath2, "utf8");
5180
5861
  const lines2 = raw.split("\n");
5181
5862
  const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
5182
5863
  if (begin === -1) return;
@@ -5190,11 +5871,11 @@ function uninstallCodexHook(configPath2) {
5190
5871
  }
5191
5872
 
5192
5873
  // src/commands/tools.ts
5193
- import { existsSync as existsSync13, readFileSync as readFileSync17 } from "fs";
5874
+ import { existsSync as existsSync13, readFileSync as readFileSync19 } from "fs";
5194
5875
  function fileContains(path, needle) {
5195
5876
  try {
5196
5877
  if (!existsSync13(path)) return false;
5197
- return readFileSync17(path, "utf8").includes(needle);
5878
+ return readFileSync19(path, "utf8").includes(needle);
5198
5879
  } catch {
5199
5880
  return false;
5200
5881
  }
@@ -5268,13 +5949,13 @@ function subscribedEvents(registry, tool, fallback) {
5268
5949
  }
5269
5950
 
5270
5951
  // src/commands/enable.ts
5271
- import { dirname as dirname9, join as join23 } from "path";
5952
+ import { dirname as dirname9, join as join26 } from "path";
5272
5953
 
5273
5954
  // src/wrap/shim.ts
5274
- import { execFileSync as execFileSync8 } from "child_process";
5275
- import { chmodSync as chmodSync7, existsSync as existsSync14, mkdirSync as mkdirSync18, readdirSync as readdirSync4, readFileSync as readFileSync18, rmdirSync, rmSync as rmSync11, writeFileSync as writeFileSync18 } from "fs";
5955
+ import { execFileSync as execFileSync9 } from "child_process";
5956
+ import { chmodSync as chmodSync8, existsSync as existsSync14, mkdirSync as mkdirSync21, readdirSync as readdirSync5, readFileSync as readFileSync20, rmdirSync, rmSync as rmSync14, writeFileSync as writeFileSync21 } from "fs";
5276
5957
  import { homedir as homedir5 } from "os";
5277
- import { delimiter as delimiter2, join as join21, resolve as resolve2 } from "path";
5958
+ import { delimiter as delimiter2, join as join24, resolve as resolve2 } from "path";
5278
5959
  var SHIM_FALLBACK_EXIT = 213;
5279
5960
  var WIN_SHIM_ENABLED = false;
5280
5961
  var SHIM_COMMANDS = { claude_code: "claude", codex: "codex" };
@@ -5284,10 +5965,10 @@ var RC_END = "# <<< deskbuddy <<<";
5284
5965
  var RC_FILES = [".zshrc", ".bashrc"];
5285
5966
  var WIN_PENDING = "Windows \u7A97\u53E3\u63A5\u7BA1\u5F85\u9A8C\u8BC1\uFF0C\u6682\u672A\u5F00\u542F";
5286
5967
  function shimDir() {
5287
- return join21(deskbuddyHome(), "bin");
5968
+ return join24(deskbuddyHome(), "bin");
5288
5969
  }
5289
5970
  function shimFile(binDir, command, platform = process.platform) {
5290
- return join21(binDir, platform === "win32" ? `${command}.cmd` : command);
5971
+ return join24(binDir, platform === "win32" ? `${command}.cmd` : command);
5291
5972
  }
5292
5973
  function shQuote3(s) {
5293
5974
  return `'${s.replace(/'/g, `'\\''`)}'`;
@@ -5416,10 +6097,10 @@ function removeRcBlock(content) {
5416
6097
  return content.slice(0, start) + content.slice(end);
5417
6098
  }
5418
6099
  function readRc(p) {
5419
- return readFileSync18(p).toString("latin1");
6100
+ return readFileSync20(p).toString("latin1");
5420
6101
  }
5421
6102
  function writeRc(p, latin1) {
5422
- writeFileSync18(p, Buffer.from(latin1, "latin1"));
6103
+ writeFileSync21(p, Buffer.from(latin1, "latin1"));
5423
6104
  }
5424
6105
  function toLatin1(utf8) {
5425
6106
  return Buffer.from(utf8, "utf8").toString("latin1");
@@ -5441,7 +6122,7 @@ function removePathEntry(value, dir) {
5441
6122
  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)))";
5442
6123
  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')";
5443
6124
  function psWinPath() {
5444
- const ps = (script, env) => execFileSync8("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
6125
+ const ps = (script, env) => execFileSync9("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
5445
6126
  encoding: "utf8",
5446
6127
  stdio: ["ignore", "pipe", "ignore"],
5447
6128
  windowsHide: true,
@@ -5488,12 +6169,12 @@ function installShim(tool, e) {
5488
6169
  if (!node || !cli || !bin) {
5489
6170
  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" };
5490
6171
  }
5491
- mkdirSync18(e.binDir, { recursive: true });
5492
- writeFileSync18(file, cmdScript(tool, command, node, cli, bin), "utf8");
6172
+ mkdirSync21(e.binDir, { recursive: true });
6173
+ writeFileSync21(file, cmdScript(tool, command, node, cli, bin), "utf8");
5493
6174
  } else {
5494
- mkdirSync18(e.binDir, { recursive: true, mode: 448 });
5495
- writeFileSync18(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
5496
- chmodSync7(file, 493);
6175
+ mkdirSync21(e.binDir, { recursive: true, mode: 448 });
6176
+ writeFileSync21(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
6177
+ chmodSync8(file, 493);
5497
6178
  }
5498
6179
  const touched = [];
5499
6180
  if (e.platform === "win32") {
@@ -5507,7 +6188,7 @@ function installShim(tool, e) {
5507
6188
  } else if (e.rcHome) {
5508
6189
  const block = toLatin1(rcBlock(e.binDir, e.rcHome));
5509
6190
  for (const name of RC_FILES) {
5510
- const rc = join21(e.rcHome, name);
6191
+ const rc = join24(e.rcHome, name);
5511
6192
  if (!existsSync14(rc)) continue;
5512
6193
  const next = addRcBlock(readRc(rc), block);
5513
6194
  if (next !== null) writeRc(rc, next);
@@ -5528,13 +6209,13 @@ function installShim(tool, e) {
5528
6209
  function ourShims(binDir) {
5529
6210
  let names;
5530
6211
  try {
5531
- names = readdirSync4(binDir);
6212
+ names = readdirSync5(binDir);
5532
6213
  } catch {
5533
6214
  return [];
5534
6215
  }
5535
6216
  return names.filter((n) => {
5536
6217
  try {
5537
- return readFileSync18(join21(binDir, n), "utf8").includes(SHIM_MARKER);
6218
+ return readFileSync20(join24(binDir, n), "utf8").includes(SHIM_MARKER);
5538
6219
  } catch {
5539
6220
  return false;
5540
6221
  }
@@ -5546,8 +6227,8 @@ function uninstallShim(tool, e) {
5546
6227
  let removed = false;
5547
6228
  const file = shimFile(e.binDir, command, e.platform);
5548
6229
  try {
5549
- if (existsSync14(file) && readFileSync18(file, "utf8").includes(SHIM_MARKER)) {
5550
- rmSync11(file, { force: true });
6230
+ if (existsSync14(file) && readFileSync20(file, "utf8").includes(SHIM_MARKER)) {
6231
+ rmSync14(file, { force: true });
5551
6232
  removed = true;
5552
6233
  }
5553
6234
  } catch {
@@ -5565,7 +6246,7 @@ function uninstallShim(tool, e) {
5565
6246
  }
5566
6247
  } else if (e.rcHome) {
5567
6248
  for (const name of RC_FILES) {
5568
- const rc = join21(e.rcHome, name);
6249
+ const rc = join24(e.rcHome, name);
5569
6250
  if (!existsSync14(rc)) continue;
5570
6251
  const next = removeRcBlock(readRc(rc));
5571
6252
  if (next !== null) {
@@ -5575,7 +6256,7 @@ function uninstallShim(tool, e) {
5575
6256
  }
5576
6257
  }
5577
6258
  try {
5578
- if (existsSync14(e.binDir) && readdirSync4(e.binDir).length === 0) rmdirSync(e.binDir);
6259
+ if (existsSync14(e.binDir) && readdirSync5(e.binDir).length === 0) rmdirSync(e.binDir);
5579
6260
  } catch {
5580
6261
  }
5581
6262
  if (!removed && touched.length === 0) return { ok: true, message: "" };
@@ -5587,7 +6268,7 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
5587
6268
  const file = shimFile(binDir, command, platform);
5588
6269
  let installed = false;
5589
6270
  try {
5590
- installed = readFileSync18(file, "utf8").includes(SHIM_MARKER);
6271
+ installed = readFileSync20(file, "utf8").includes(SHIM_MARKER);
5591
6272
  } catch {
5592
6273
  installed = false;
5593
6274
  }
@@ -5598,16 +6279,16 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
5598
6279
  }
5599
6280
 
5600
6281
  // src/wrap/pty.ts
5601
- import { chmodSync as chmodSync8, existsSync as existsSync15, statSync as statSync7 } from "fs";
6282
+ import { chmodSync as chmodSync9, existsSync as existsSync15, statSync as statSync8 } from "fs";
5602
6283
  import { createRequire as createRequire2 } from "module";
5603
- import { dirname as dirname8, join as join22 } from "path";
6284
+ import { dirname as dirname8, join as join25 } from "path";
5604
6285
  var req = createRequire2(import.meta.url);
5605
6286
  function fixSpawnHelper() {
5606
6287
  if (process.platform !== "darwin") return;
5607
6288
  try {
5608
- const dir = join22(dirname8(req.resolve("node-pty/package.json")), "prebuilds", `darwin-${process.arch}`);
5609
- const helper = join22(dir, "spawn-helper");
5610
- if (existsSync15(helper) && (statSync7(helper).mode & 73) === 0) chmodSync8(helper, 493);
6289
+ const dir = join25(dirname8(req.resolve("node-pty/package.json")), "prebuilds", `darwin-${process.arch}`);
6290
+ const helper = join25(dir, "spawn-helper");
6291
+ if (existsSync15(helper) && (statSync8(helper).mode & 73) === 0) chmodSync9(helper, 493);
5611
6292
  } catch {
5612
6293
  }
5613
6294
  }
@@ -5637,6 +6318,31 @@ function screenLines(term) {
5637
6318
  }
5638
6319
  return out;
5639
6320
  }
6321
+ function screenLinesUndimmed(term) {
6322
+ const b = term.buffer.active;
6323
+ const out = [];
6324
+ for (let i = b.viewportY; i < b.viewportY + term.rows; i++) {
6325
+ const l = b.getLine(i);
6326
+ if (!l) {
6327
+ out.push("");
6328
+ continue;
6329
+ }
6330
+ if (typeof l.getCell !== "function") {
6331
+ out.push(l.translateToString(true));
6332
+ continue;
6333
+ }
6334
+ let s = "";
6335
+ for (let x = 0; x < term.cols; x++) {
6336
+ const c = l.getCell(x);
6337
+ if (!c) break;
6338
+ const w = c.getWidth();
6339
+ if (w === 0) continue;
6340
+ s += c.isDim() ? " ".repeat(w) : c.getChars() || " ";
6341
+ }
6342
+ out.push(s.trimEnd());
6343
+ }
6344
+ return out;
6345
+ }
5640
6346
 
5641
6347
  // src/commands/enable.ts
5642
6348
  function stdout(line) {
@@ -5702,14 +6408,14 @@ function installTools(tools, out, shim = defaultShimEnvForInstall()) {
5702
6408
  installed.push(tool);
5703
6409
  out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
5704
6410
  try {
5705
- if (adapter.installWindowGate?.(configPath2, join23(dirname9(hookPath), "gate.cjs"), tool)) {
6411
+ if (adapter.installWindowGate?.(configPath2, join26(dirname9(hookPath), "gate.cjs"), tool)) {
5706
6412
  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`);
5707
6413
  }
5708
6414
  } catch (e) {
5709
6415
  out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
5710
6416
  }
5711
6417
  try {
5712
- if (adapter.installRewake?.(configPath2, join23(dirname9(hookPath), "wake.cjs"), tool)) {
6418
+ if (adapter.installRewake?.(configPath2, join26(dirname9(hookPath), "wake.cjs"), tool)) {
5713
6419
  out(` \u7A97\u53E3\u5F00\u7740\u65F6\uFF0C\u8BBE\u5907\u6307\u4EE4\u76F4\u63A5\u5728\u8FD9\u4E2A\u7A97\u53E3\u91CC\u6267\u884C\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
5714
6420
  }
5715
6421
  } catch (e) {
@@ -5809,8 +6515,8 @@ async function runDisable(tools, opts = {}) {
5809
6515
  }
5810
6516
 
5811
6517
  // src/commands/install.ts
5812
- import { mkdirSync as mkdirSync19, rmSync as rmSync12, writeFileSync as writeFileSync19 } from "fs";
5813
- import { join as join24 } from "path";
6518
+ import { mkdirSync as mkdirSync22, rmSync as rmSync15, writeFileSync as writeFileSync22 } from "fs";
6519
+ import { join as join27 } from "path";
5814
6520
 
5815
6521
  // src/core/detect.ts
5816
6522
  import { existsSync as existsSync16 } from "fs";
@@ -5855,7 +6561,7 @@ function detectTools(d = defaultDetectDeps()) {
5855
6561
  import qrcode from "qrcode-terminal";
5856
6562
 
5857
6563
  // src/commands/remoteCwd.ts
5858
- import { existsSync as existsSync17, statSync as statSync8 } from "fs";
6564
+ import { existsSync as existsSync17, statSync as statSync9 } from "fs";
5859
6565
  import { createInterface } from "readline";
5860
6566
  import { homedir as homedir6 } from "os";
5861
6567
  import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
@@ -5869,7 +6575,7 @@ function normalizePath(raw, home = homedir6()) {
5869
6575
  function checkPath(abs) {
5870
6576
  if (!existsSync17(abs)) return "missing";
5871
6577
  try {
5872
- return statSync8(abs).isDirectory() ? "ok" : "not_a_dir";
6578
+ return statSync9(abs).isDirectory() ? "ok" : "not_a_dir";
5873
6579
  } catch {
5874
6580
  return "missing";
5875
6581
  }
@@ -6013,6 +6719,8 @@ async function runSetup(opts = {}) {
6013
6719
  bound.remoteCwd = remoteCwd;
6014
6720
  saveConfig(bound);
6015
6721
  out(` \u5DF2\u8BB0\u4E0B\uFF1A${remoteCwd}`);
6722
+ out(" \u63D0\u793A\uFF1AClaude Code \u7B2C\u4E00\u6B21\u5728\u65B0\u76EE\u5F55\u91CC\u542F\u52A8\u4F1A\u95EE\u300C\u662F\u5426\u4FE1\u4EFB\u6B64\u6587\u4EF6\u5939\u300D\uFF0CDeskBuddy \u4E0D\u4F1A\u66FF\u4F60\u56DE\u7B54\u3002");
6723
+ out(" \u5EFA\u8BAE\u5148\u5728\u8FD9\u4E2A\u76EE\u5F55\u91CC\u624B\u52A8\u8FD0\u884C\u4E00\u6B21 claude \u5E76\u9009\u62E9\u4FE1\u4EFB\uFF0C\u5426\u5219\u65B0\u5EFA\u4EFB\u52A1\u8981\u7B49\u4F60\u5728\u7535\u8111\u4E0A\u70B9\u4E86\u624D\u4F1A\u5F00\u59CB\u3002");
6016
6724
  out("");
6017
6725
  out("\u2705 \u6388\u6743\u6210\u529F\u3002\u63A5\u4E0B\u6765\u8FD0\u884C\uFF1Adeskbuddy enable claude_code,codex");
6018
6726
  return 0;
@@ -6071,11 +6779,11 @@ function inSandbox(env) {
6071
6779
  }
6072
6780
  function precheckWritable(dirs) {
6073
6781
  for (const dir of dirs) {
6074
- const probe = join24(dir, `.deskbuddy-probe-${process.pid}`);
6782
+ const probe = join27(dir, `.deskbuddy-probe-${process.pid}`);
6075
6783
  try {
6076
- mkdirSync19(dir, { recursive: true });
6077
- writeFileSync19(probe, "");
6078
- rmSync12(probe, { force: true });
6784
+ mkdirSync22(dir, { recursive: true });
6785
+ writeFileSync22(probe, "");
6786
+ rmSync15(probe, { force: true });
6079
6787
  } catch (e) {
6080
6788
  return { ok: false, dir, error: e.message };
6081
6789
  }
@@ -6204,7 +6912,7 @@ async function runInstall(opts, deps = defaultInstallDeps()) {
6204
6912
 
6205
6913
  // src/commands/logout.ts
6206
6914
  import { createInterface as createInterface2 } from "readline";
6207
- import { existsSync as existsSync18, rmSync as rmSync13 } from "fs";
6915
+ import { existsSync as existsSync18, rmSync as rmSync16 } from "fs";
6208
6916
  function stdout4(line) {
6209
6917
  process.stdout.write(line + "\n");
6210
6918
  }
@@ -6236,7 +6944,7 @@ async function runLogout(opts = {}) {
6236
6944
  }
6237
6945
  await runDisable([], { out, brief: true, keepAutostart: opts.keepAutostart });
6238
6946
  if (existsSync18(home)) {
6239
- rmSync13(home, { recursive: true, force: true });
6947
+ rmSync16(home, { recursive: true, force: true });
6240
6948
  }
6241
6949
  out("");
6242
6950
  out("\u2705 \u5DF2\u6E05\u9664\u672C\u673A\u7684 DeskBuddy \u6570\u636E\u3002");
@@ -6396,45 +7104,15 @@ async function runUpdate(opts = {}) {
6396
7104
  }
6397
7105
 
6398
7106
  // src/wrap/run.ts
6399
- import { randomBytes as randomBytes7 } from "crypto";
6400
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync21, statSync as statSync10, truncateSync as truncateSync3 } from "fs";
6401
- import { join as join26 } from "path";
7107
+ import { randomBytes as randomBytes8 } from "crypto";
7108
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync24, statSync as statSync11, truncateSync as truncateSync3 } from "fs";
7109
+ import { join as join29 } from "path";
6402
7110
  import { StringDecoder } from "string_decoder";
6403
7111
 
6404
- // src/wrap/screen.ts
6405
- var ESC = "\x1B";
6406
- var PASTE_BEGIN = ESC + "[200~";
6407
- var PASTE_END = ESC + "[201~";
6408
- function sanitize(text) {
6409
- return String(text).replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
6410
- }
6411
- function prepare(text) {
6412
- return " " + sanitize(text).trim();
6413
- }
6414
- var PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
6415
- function inspectLines(lines2) {
6416
- const text = lines2.join("\n");
6417
- const permission = PERMISSION_RE.test(text);
6418
- const rules = [];
6419
- lines2.forEach((l, i) => {
6420
- if (/^─{20,}/.test(l.trim())) rules.push(i);
6421
- });
6422
- let input = null;
6423
- if (rules.length >= 2) {
6424
- const a = rules[rules.length - 2];
6425
- const b = rules[rules.length - 1];
6426
- input = lines2.slice(a + 1, b).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
6427
- }
6428
- return { permission, input };
6429
- }
6430
- function boxMatches(view, body) {
6431
- if (view.input === null || view.permission) return false;
6432
- const flat = (x) => x.replace(/\s+/g, "");
6433
- return flat(view.input) === flat(body) || /\[Pasted text #\d+/.test(view.input);
6434
- }
6435
-
6436
7112
  // src/wrap/injector.ts
7113
+ var SEEN_MAX = 200;
6437
7114
  var QUIET_MS = 1500;
7115
+ var STARTUP_QUIET_MS = 3e3;
6438
7116
  var KEY_QUIET_MS = 3e3;
6439
7117
  var PASTE_SETTLE_MS = 400;
6440
7118
  var SUBMIT_WAIT_MS = 1e4;
@@ -6446,6 +7124,15 @@ var Injector = class {
6446
7124
  #lastKey = 0;
6447
7125
  #busy = false;
6448
7126
  #closed = false;
7127
+ #lastWhy = "";
7128
+ /** 用户在这个窗口里按过键(终端报告不算) */
7129
+ #userKeyed = false;
7130
+ /** 本窗口已经写进过一条 */
7131
+ #injectedOnce = false;
7132
+ /** 出现过信任框:之后窗口关闭多半是用户选了 No / 不想在这个目录里跑,不能交回后台 */
7133
+ #sawTrust = false;
7134
+ /** instructionId → 那一条(去重:daemon 等 ack 超时重送时,不能写进窗口两遍) */
7135
+ #seen = /* @__PURE__ */ new Map();
6449
7136
  constructor(deps) {
6450
7137
  this.#d = deps;
6451
7138
  this.#lastOut = deps.now();
@@ -6453,8 +7140,11 @@ var Injector = class {
6453
7140
  noteOutput() {
6454
7141
  this.#lastOut = this.#d.now();
6455
7142
  }
6456
- noteKey() {
7143
+ /** 终端输入(不传 = 按一次真按键算)。只有终端自己回的报告(焦点进出、光标位置等)不算按键 */
7144
+ noteKey(input) {
7145
+ if (input !== void 0 && isTerminalReport(input)) return;
6457
7146
  this.#lastKey = this.#d.now();
7147
+ this.#userKeyed = true;
6458
7148
  }
6459
7149
  get pending() {
6460
7150
  return this.#queue.length;
@@ -6465,7 +7155,28 @@ var Injector = class {
6465
7155
  if (i >= 0) this.#queue.splice(i, 1);
6466
7156
  if (job.done) return;
6467
7157
  job.done = true;
6468
- job.reply({ instructionId: job.instructionId, ...msg });
7158
+ this.#d.log?.(`\u7ED3\u5C40 ${job.instructionId} ${msg.status}${msg.reason ? "/" + msg.reason : ""}`);
7159
+ job.finalMsg = { instructionId: job.instructionId, ...msg };
7160
+ job.reply(job.finalMsg);
7161
+ }
7162
+ /** 记下这条,超过上限丢最早的 */
7163
+ #remember(job) {
7164
+ this.#seen.set(job.instructionId, job);
7165
+ while (this.#seen.size > SEEN_MAX) this.#seen.delete(this.#seen.keys().next().value);
7166
+ }
7167
+ /**
7168
+ * 同一 instructionId 又送来了(daemon 没等到 ack 重送、而第一次其实已收下):不再排队,
7169
+ * 把后续进展改回给新连接;已有终态的原样再回一次
7170
+ */
7171
+ #rebind(old, job) {
7172
+ job.reply({ instructionId: job.instructionId, status: "ack" });
7173
+ if (old.done) {
7174
+ if (old.finalMsg) job.reply(old.finalMsg);
7175
+ return;
7176
+ }
7177
+ old.reply = job.reply;
7178
+ old.alive = job.alive;
7179
+ if (old.queuedSent) job.reply({ instructionId: job.instructionId, status: "queued", mode: "window", reason: "" });
6469
7180
  }
6470
7181
  /**
6471
7182
  * 这个会话还是不是本窗口当前的会话。
@@ -6476,19 +7187,36 @@ var Injector = class {
6476
7187
  #owns(sessionId) {
6477
7188
  const last = this.#d.lastEvent();
6478
7189
  if (!last) return true;
7190
+ if (!sessionId) return last.ev !== "SessionEnd";
6479
7191
  return last.sid === sessionId && last.ev !== "SessionEnd";
6480
7192
  }
6481
7193
  /** 通道收到一条指令 */
6482
7194
  submit(job) {
7195
+ const seen = this.#seen.get(job.instructionId);
7196
+ if (seen) {
7197
+ this.#rebind(seen, job);
7198
+ return;
7199
+ }
6483
7200
  if (this.#closed) {
6484
7201
  job.reply({ instructionId: job.instructionId, status: "not_here" });
6485
7202
  return;
6486
7203
  }
7204
+ if (tooLongToInject(job.text)) {
7205
+ this.#remember(job);
7206
+ job.done = true;
7207
+ job.finalMsg = { instructionId: job.instructionId, status: "rejected", mode: "window", reason: TEXT_TOO_LONG };
7208
+ job.reply(job.finalMsg);
7209
+ return;
7210
+ }
6487
7211
  if (!this.#owns(job.sessionId)) {
7212
+ this.#remember(job);
6488
7213
  job.done = true;
6489
- job.reply({ instructionId: job.instructionId, status: "rejected", mode: "window", reason: "session_gone" });
7214
+ job.finalMsg = { instructionId: job.instructionId, status: "rejected", mode: "window", reason: "session_gone" };
7215
+ job.reply(job.finalMsg);
6490
7216
  return;
6491
7217
  }
7218
+ this.#remember(job);
7219
+ this.#d.log?.(`\u6536\u5230 ${job.instructionId} len=${job.text.length} head=${JSON.stringify(job.text.slice(0, 12))}`);
6492
7220
  job.phase = "queued";
6493
7221
  job.reply({ instructionId: job.instructionId, status: "ack" });
6494
7222
  this.#queue.push(job);
@@ -6505,14 +7233,20 @@ var Injector = class {
6505
7233
  const why = [];
6506
7234
  const v = this.#d.view();
6507
7235
  if (!v) return ["no-screen"];
7236
+ const last = this.#d.lastEvent();
7237
+ if (v.trust) {
7238
+ why.push("trust-dialog");
7239
+ this.#sawTrust = true;
7240
+ }
6508
7241
  if (v.permission) why.push("permission");
7242
+ const pristine = this.#d.startupIdle === true && !this.#userKeyed && !this.#injectedOnce && !last;
6509
7243
  if (v.input === null) why.push("no-input-box");
6510
- else if (v.input !== "") why.push("user-typing");
6511
- const last = this.#d.lastEvent();
6512
- if (!last) why.push("hook:none");
6513
- else if (!IDLE_EVENTS.has(last.ev)) why.push("hook:" + last.ev);
7244
+ else if (v.input !== "" && !v.placeholder && !pristine) why.push("user-typing");
7245
+ if (!last) {
7246
+ if (!this.#d.startupIdle) why.push("hook:none");
7247
+ } else if (!IDLE_EVENTS.has(last.ev)) why.push("hook:" + last.ev);
6514
7248
  const now = this.#d.now();
6515
- if (now - this.#lastOut < QUIET_MS) why.push("screen-busy");
7249
+ if (now - this.#lastOut < (last ? QUIET_MS : STARTUP_QUIET_MS)) why.push("screen-busy");
6516
7250
  if (now - this.#lastKey < KEY_QUIET_MS) why.push("recent-key");
6517
7251
  return why;
6518
7252
  }
@@ -6523,6 +7257,7 @@ var Injector = class {
6523
7257
  if (job.alive && !job.alive()) {
6524
7258
  this.#queue.shift();
6525
7259
  job.done = true;
7260
+ this.#seen.delete(job.instructionId);
6526
7261
  this.#d.log?.("daemon \u8FDE\u63A5\u5DF2\u65AD\uFF0C\u4E22\u5F03\u672A\u5199\u5165\u7684\u4E00\u6761");
6527
7262
  return;
6528
7263
  }
@@ -6542,9 +7277,16 @@ var Injector = class {
6542
7277
  this.#d.log?.(`\u7A7A\u95F2\u5224\u65AD\u51FA\u9519\uFF1A${e.message}`);
6543
7278
  }
6544
7279
  if (why.length > 0) {
7280
+ const key = why.join(",");
7281
+ if (key !== this.#lastWhy) {
7282
+ this.#lastWhy = key;
7283
+ const v = this.#d.view();
7284
+ this.#d.log?.(`\u6392\u961F\u4E2D\uFF1A${key} input=${v?.input === null ? "null" : JSON.stringify((v?.input ?? "").slice(0, 12)) + "/" + (v?.input ?? "").length} last=${this.#d.lastEvent()?.ev ?? "none"}`);
7285
+ }
6545
7286
  this.#markQueued(job);
6546
7287
  return;
6547
7288
  }
7289
+ this.#lastWhy = "";
6548
7290
  this.#busy = true;
6549
7291
  try {
6550
7292
  await this.#inject(job);
@@ -6557,6 +7299,8 @@ var Injector = class {
6557
7299
  }
6558
7300
  async #inject(job) {
6559
7301
  const body = prepare(job.text);
7302
+ this.#injectedOnce = true;
7303
+ this.#d.log?.(`\u5199\u5165 ${job.instructionId} len=${body.length - 1}`);
6560
7304
  let enterAt = 0;
6561
7305
  this.#d.holdKeys(true);
6562
7306
  try {
@@ -6603,14 +7347,15 @@ var Injector = class {
6603
7347
  for (const job of [...this.#queue]) {
6604
7348
  if (job.phase === "entered") this.#final(job, { status: "started", mode: "window", reason: "" });
6605
7349
  else if (job.phase === "pasted") this.#final(job, { status: "failed", mode: "window", reason: "window_failed" });
7350
+ else if (this.#sawTrust) this.#final(job, { status: "rejected", mode: "window", reason: "window_failed" });
6606
7351
  else this.#final(job, { status: "not_here" });
6607
7352
  }
6608
7353
  }
6609
7354
  };
6610
7355
 
6611
7356
  // src/wrap/passthrough.ts
6612
- import { spawn as spawn2 } from "child_process";
6613
- import { realpathSync as realpathSync2 } from "fs";
7357
+ import { spawn as spawn3 } from "child_process";
7358
+ import { realpathSync as realpathSync3 } from "fs";
6614
7359
  import { constants as constants2 } from "os";
6615
7360
  import { dirname as dirname11, resolve as resolve4 } from "path";
6616
7361
  var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
@@ -6668,7 +7413,7 @@ function norm(p, win) {
6668
7413
  }
6669
7414
  function real(p) {
6670
7415
  try {
6671
- return realpathSync2(p);
7416
+ return realpathSync3(p);
6672
7417
  } catch {
6673
7418
  return p;
6674
7419
  }
@@ -6683,6 +7428,9 @@ function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
6683
7428
  const loc = locate(tool, dd);
6684
7429
  return loc && !inSkip(loc.prefixArgs[0] && /\.(?:c|m)?js$/i.test(loc.prefixArgs[0]) ? loc.prefixArgs[0] : loc.file) ? loc : null;
6685
7430
  }
7431
+ if (win && tool === "claude_code") {
7432
+ for (const p of winClaudeExeCandidates(dd)) if (dd.exists(p) && !inSkip(p)) return { file: p, prefixArgs: [] };
7433
+ }
6686
7434
  for (const f of whichAllIn(name, dd)) {
6687
7435
  if (inSkip(f)) continue;
6688
7436
  const loc = win ? resolveWinLaunch(f, dd) : { file: f, prefixArgs: [] };
@@ -6702,7 +7450,7 @@ function runDirect(real2, args, env = process.env) {
6702
7450
  return new Promise((done) => {
6703
7451
  let child;
6704
7452
  try {
6705
- child = spawn2(real2.file, [...real2.prefixArgs, ...args], {
7453
+ child = spawn3(real2.file, [...real2.prefixArgs, ...args], {
6706
7454
  stdio: "inherit",
6707
7455
  // 🔴 再经过垫片也直接放行,不会绕回来
6708
7456
  env: withoutShimFlag({ ...env, DESKBUDDY_WRAPPED: "1" }),
@@ -6752,21 +7500,21 @@ function runDirect(real2, args, env = process.env) {
6752
7500
  }
6753
7501
 
6754
7502
  // src/wrap/codexLaunch.ts
6755
- import { spawn as spawn3 } from "child_process";
6756
- import { closeSync as closeSync2, mkdirSync as mkdirSync20, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync19, rmSync as rmSync14, statSync as statSync9, truncateSync as truncateSync2, writeFileSync as writeFileSync20, renameSync as renameSync14 } from "fs";
7503
+ import { spawn as spawn4 } from "child_process";
7504
+ import { closeSync as closeSync3, mkdirSync as mkdirSync23, openSync as openSync3, readdirSync as readdirSync6, readFileSync as readFileSync21, rmSync as rmSync17, statSync as statSync10, truncateSync as truncateSync2, writeFileSync as writeFileSync23, renameSync as renameSync16 } from "fs";
6757
7505
  import { request } from "http";
6758
7506
  import { createServer as createServer4 } from "net";
6759
- import { join as join25 } from "path";
7507
+ import { join as join28 } from "path";
6760
7508
 
6761
7509
  // src/core/codexServer.ts
6762
- import { execFileSync as execFileSync9 } from "child_process";
7510
+ import { execFileSync as execFileSync10 } from "child_process";
6763
7511
  function isAppServerArgs(args, url) {
6764
7512
  return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
6765
7513
  }
6766
7514
  function processArgs(pid) {
6767
7515
  if (process.platform === "win32") return null;
6768
7516
  try {
6769
- const out = execFileSync9("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
7517
+ const out = execFileSync10("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
6770
7518
  encoding: "utf8",
6771
7519
  stdio: ["ignore", "pipe", "ignore"]
6772
7520
  });
@@ -6924,10 +7672,10 @@ async function runCodexWindow(real2, args, d) {
6924
7672
  }
6925
7673
  var MAX_LOG_BYTES2 = 1024 * 1024;
6926
7674
  function appServerLogPath() {
6927
- return join25(deskbuddyHome(), "codex-app-server.log");
7675
+ return join28(deskbuddyHome(), "codex-app-server.log");
6928
7676
  }
6929
7677
  function windowsDir2() {
6930
- return join25(deskbuddyHome(), "codex-windows");
7678
+ return join28(deskbuddyHome(), "codex-windows");
6931
7679
  }
6932
7680
  function pidAlive(pid) {
6933
7681
  try {
@@ -6940,7 +7688,7 @@ function pidAlive(pid) {
6940
7688
  function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
6941
7689
  let pid = 0;
6942
7690
  try {
6943
- pid = Number(readFileSync19(pidFile, "utf8").trim());
7691
+ pid = Number(readFileSync21(pidFile, "utf8").trim());
6944
7692
  } catch {
6945
7693
  return false;
6946
7694
  }
@@ -6974,16 +7722,16 @@ function freePort() {
6974
7722
  }
6975
7723
  function startServer(real2, url, env, cwd) {
6976
7724
  const logPath2 = appServerLogPath();
6977
- mkdirSync20(deskbuddyHome(), { recursive: true });
7725
+ mkdirSync23(deskbuddyHome(), { recursive: true });
6978
7726
  try {
6979
- if (statSync9(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
7727
+ if (statSync10(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
6980
7728
  } catch {
6981
7729
  }
6982
- const fd = openSync2(logPath2, "a");
7730
+ const fd = openSync3(logPath2, "a");
6983
7731
  try {
6984
7732
  const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
6985
7733
  delete childEnv2.DESKBUDDY_SHIM;
6986
- const child = spawn3(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
7734
+ const child = spawn4(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
6987
7735
  detached: true,
6988
7736
  stdio: ["ignore", fd, fd],
6989
7737
  cwd,
@@ -6994,7 +7742,7 @@ function startServer(real2, url, env, cwd) {
6994
7742
  child.unref();
6995
7743
  return child.pid ?? null;
6996
7744
  } finally {
6997
- closeSync2(fd);
7745
+ closeSync3(fd);
6998
7746
  }
6999
7747
  }
7000
7748
  function killServer(pid) {
@@ -7008,41 +7756,41 @@ function killOwnServer(pid, url) {
7008
7756
  if (a && isAppServerArgs(a, url)) killServer(pid);
7009
7757
  }
7010
7758
  function recordPath(rec) {
7011
- return join25(windowsDir2(), `${rec.server}.json`);
7759
+ return join28(windowsDir2(), `${rec.server}.json`);
7012
7760
  }
7013
7761
  function writeRecord(rec) {
7014
7762
  try {
7015
- mkdirSync20(windowsDir2(), { recursive: true, mode: 448 });
7763
+ mkdirSync23(windowsDir2(), { recursive: true, mode: 448 });
7016
7764
  const p = recordPath(rec);
7017
- writeFileSync20(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
7018
- renameSync14(p + ".tmp", p);
7765
+ writeFileSync23(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
7766
+ renameSync16(p + ".tmp", p);
7019
7767
  } catch {
7020
7768
  }
7021
7769
  }
7022
7770
  function removeRecord(rec) {
7023
7771
  try {
7024
- rmSync14(recordPath(rec), { force: true });
7772
+ rmSync17(recordPath(rec), { force: true });
7025
7773
  } catch {
7026
7774
  }
7027
7775
  }
7028
7776
  function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs, kill = killServer) {
7029
7777
  let names = [];
7030
7778
  try {
7031
- names = readdirSync5(dir).filter((n) => n.endsWith(".json"));
7779
+ names = readdirSync6(dir).filter((n) => n.endsWith(".json"));
7032
7780
  } catch {
7033
7781
  return;
7034
7782
  }
7035
7783
  for (const n of names) {
7036
- const p = join25(dir, n);
7784
+ const p = join28(dir, n);
7037
7785
  let rec;
7038
7786
  try {
7039
- rec = JSON.parse(readFileSync19(p, "utf8"));
7787
+ rec = JSON.parse(readFileSync21(p, "utf8"));
7040
7788
  } catch {
7041
- rmSync14(p, { force: true });
7789
+ rmSync17(p, { force: true });
7042
7790
  continue;
7043
7791
  }
7044
7792
  if (typeof rec.supervisor !== "number" || typeof rec.server !== "number" || typeof rec.url !== "string") {
7045
- rmSync14(p, { force: true });
7793
+ rmSync17(p, { force: true });
7046
7794
  continue;
7047
7795
  }
7048
7796
  if (alive(rec.supervisor)) continue;
@@ -7050,13 +7798,13 @@ function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs,
7050
7798
  const a = args(rec.server);
7051
7799
  if (a && isAppServerArgs(a, rec.url)) kill(rec.server);
7052
7800
  }
7053
- rmSync14(p, { force: true });
7801
+ rmSync17(p, { force: true });
7054
7802
  }
7055
7803
  }
7056
7804
  function launchLog(line) {
7057
7805
  try {
7058
- mkdirSync20(deskbuddyHome(), { recursive: true });
7059
- writeFileSync20(join25(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
7806
+ mkdirSync23(deskbuddyHome(), { recursive: true });
7807
+ writeFileSync23(join28(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
7060
7808
  `, { flag: "a" });
7061
7809
  } catch {
7062
7810
  }
@@ -7096,10 +7844,10 @@ function childEnv(src, windowId) {
7096
7844
  }
7097
7845
  function wrapLog(line) {
7098
7846
  try {
7099
- const p = join26(deskbuddyHome(), "wrap.log");
7100
- mkdirSync21(deskbuddyHome(), { recursive: true });
7847
+ const p = join29(deskbuddyHome(), "wrap.log");
7848
+ mkdirSync24(deskbuddyHome(), { recursive: true });
7101
7849
  try {
7102
- if (statSync10(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
7850
+ if (statSync11(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
7103
7851
  } catch {
7104
7852
  }
7105
7853
  appendFileSync2(p, `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${line}
@@ -7121,6 +7869,7 @@ var defaultWrapDeps = () => ({
7121
7869
  log: wrapLog
7122
7870
  });
7123
7871
  async function runWrap(argv, deps = defaultWrapDeps()) {
7872
+ if (argv[0] === "--launch") return runLaunch(argv.slice(1), deps);
7124
7873
  const viaShim = deps.env.DESKBUDDY_SHIM === "1";
7125
7874
  const r = await runWrapInner(argv, deps);
7126
7875
  if (r === "not_found") {
@@ -7178,7 +7927,59 @@ async function runWrapInner(argv, deps) {
7178
7927
  if (r !== null) return r;
7179
7928
  return deps.runDirect(real2, args);
7180
7929
  }
7181
- async function wrapInPty(tool, real2, args, deps) {
7930
+ var defaultLaunchDeps = () => ({
7931
+ claim: claimLaunch,
7932
+ isDir: isDirSync,
7933
+ chdir: (p) => process.chdir(p)
7934
+ });
7935
+ async function runLaunch(argv, deps = defaultWrapDeps(), ld = defaultLaunchDeps()) {
7936
+ const runId = argv[0] ?? "";
7937
+ const hi = argv.indexOf("--home");
7938
+ const home = hi >= 0 ? argv[hi + 1] ?? "" : "";
7939
+ if (!RUN_ID_RE.test(runId)) {
7940
+ deps.stderr.write("deskbuddy: \u542F\u52A8\u53C2\u6570\u4E0D\u5BF9\n");
7941
+ return 2;
7942
+ }
7943
+ if (home) {
7944
+ if (!/^(?:[A-Za-z]:[\\/]|\/)/.test(home)) {
7945
+ deps.stderr.write("deskbuddy: \u542F\u52A8\u53C2\u6570\u4E0D\u5BF9\n");
7946
+ return 2;
7947
+ }
7948
+ process.env.DESKBUDDY_HOME = home;
7949
+ deps.env.DESKBUDDY_HOME = home;
7950
+ }
7951
+ const plan = ld.claim(runId);
7952
+ if (!plan) {
7953
+ 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");
7954
+ return 1;
7955
+ }
7956
+ if (!ld.isDir(plan.cwd)) {
7957
+ deps.stderr.write("deskbuddy: \u5DE5\u4F5C\u76EE\u5F55\u5DF2\u7ECF\u4E0D\u5728\u4E86\n");
7958
+ return 1;
7959
+ }
7960
+ ld.chdir(plan.cwd);
7961
+ const args = plan.resume ? ["--resume", plan.resume] : [];
7962
+ let real2 = null;
7963
+ try {
7964
+ real2 = deps.findReal("claude_code");
7965
+ } catch {
7966
+ real2 = null;
7967
+ }
7968
+ if (!real2) {
7969
+ deps.stderr.write("deskbuddy: \u627E\u4E0D\u5230 claude\n");
7970
+ return 127;
7971
+ }
7972
+ const d2 = { ...deps, cwd: () => plan.cwd };
7973
+ let r = null;
7974
+ try {
7975
+ r = await wrapInPty("claude_code", real2, args, d2, { windowId: runId, launched: true });
7976
+ } catch (e) {
7977
+ deps.log(`\u5F00\u7A97\u5305\u88C5\u5931\u8D25\uFF0C\u76F4\u63A5\u8FD0\u884C\uFF1A${e.message}`);
7978
+ }
7979
+ if (r !== null) return r;
7980
+ return deps.runDirect(real2, args);
7981
+ }
7982
+ async function wrapInPty(tool, real2, args, deps, opts = {}) {
7182
7983
  const pty = deps.loadPty();
7183
7984
  const Headless = deps.loadHeadless();
7184
7985
  if (!pty || !Headless) {
@@ -7186,7 +7987,7 @@ async function wrapInPty(tool, real2, args, deps) {
7186
7987
  return null;
7187
7988
  }
7188
7989
  const { stdout: stdout7 } = deps;
7189
- const windowId = randomBytes7(16).toString("hex");
7990
+ const windowId = opts.windowId ?? randomBytes8(16).toString("hex");
7190
7991
  const cols = stdout7.columns || 80;
7191
7992
  const rows = stdout7.rows || 24;
7192
7993
  const term = new Headless({ cols, rows, allowProposedApi: true });
@@ -7203,9 +8004,9 @@ async function wrapInPty(tool, real2, args, deps) {
7203
8004
  deps.log(`pty \u8D77\u4E0D\u6765\uFF0C\u653E\u884C\u76F4\u8DD1\uFF1A${e.message}`);
7204
8005
  return null;
7205
8006
  }
7206
- return runAttached(tool, p, term, windowId, deps);
8007
+ return runAttached(tool, p, term, windowId, deps, opts.launched === true);
7207
8008
  }
7208
- function runAttached(tool, p, term0, windowId, deps) {
8009
+ function runAttached(tool, p, term0, windowId, deps, launched = false) {
7209
8010
  const { stdin, stdout: stdout7 } = deps;
7210
8011
  let term = term0;
7211
8012
  let finished = false;
@@ -7222,7 +8023,7 @@ function runAttached(tool, p, term0, windowId, deps) {
7222
8023
  };
7223
8024
  const injector = new Injector({
7224
8025
  now: () => Date.now(),
7225
- view: () => term ? inspectLines(screenLines(term)) : null,
8026
+ view: () => term ? inspectLines(screenLines(term), screenLinesUndimmed(term)) : null,
7226
8027
  lastEvent: () => readLastEvent(windowId),
7227
8028
  write: toPty,
7228
8029
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
@@ -7235,7 +8036,8 @@ function runAttached(tool, p, term0, windowId, deps) {
7235
8036
  held = null;
7236
8037
  for (const s of h) toPty(s);
7237
8038
  },
7238
- log: (m) => deps.log(`[${windowId.slice(0, 8)}] ${m}`)
8039
+ log: (m) => deps.log(`[${windowId.slice(0, 8)}] ${m}`),
8040
+ startupIdle: launched
7239
8041
  });
7240
8042
  p.onData((d) => {
7241
8043
  try {
@@ -7252,8 +8054,8 @@ function runAttached(tool, p, term0, windowId, deps) {
7252
8054
  injector.noteOutput();
7253
8055
  });
7254
8056
  const onStdin = (b) => {
7255
- injector.noteKey();
7256
8057
  const s = typeof b === "string" ? b : decoder.write(b);
8058
+ injector.noteKey(s);
7257
8059
  if (!s) return;
7258
8060
  if (held) held.push(s);
7259
8061
  else toPty(s);
@@ -7292,8 +8094,18 @@ function runAttached(tool, p, term0, windowId, deps) {
7292
8094
  } catch (e) {
7293
8095
  deps.log(`\u63A5\u7BA1\u7EC8\u7AEF\u51FA\u9519\uFF1A${e.message}`);
7294
8096
  }
8097
+ const secret = randomBytes8(32);
7295
8098
  try {
7296
- writeWindow(windowId, { pid: process.pid, childPid: p.pid, sock, tool, cwd: deps.cwd(), at: Date.now() });
8099
+ writeWindow(windowId, {
8100
+ pid: process.pid,
8101
+ childPid: p.pid,
8102
+ sock,
8103
+ tool,
8104
+ cwd: deps.cwd(),
8105
+ at: Date.now(),
8106
+ secret: secret.toString("hex"),
8107
+ ...launched ? { launched: true } : {}
8108
+ });
7297
8109
  } catch (e) {
7298
8110
  deps.log(`\u767B\u8BB0\u7A97\u53E3\u5931\u8D25\uFF1A${e.message}`);
7299
8111
  }
@@ -7301,6 +8113,7 @@ function runAttached(tool, p, term0, windowId, deps) {
7301
8113
  if (channelOk) {
7302
8114
  void startChannelServer(
7303
8115
  sock,
8116
+ secret,
7304
8117
  (req2, reply, alive) => {
7305
8118
  try {
7306
8119
  injector.submit({ ...req2, expireAt: req2.expireAt || Date.now() + DEFAULT_TTL_MS, reply, alive });