deskbuddy 0.2.0-beta.3 → 0.2.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands/daemon.ts
4
- import { writeFileSync as 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,30 +3401,236 @@ 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";
3429
+
3430
+ // src/core/wake.ts
3431
+ import { execFileSync as execFileSync7 } from "child_process";
3432
+ import { createHash as createHash3, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
3433
+ import { chmodSync as chmodSync2, closeSync as closeSync2, mkdirSync as mkdirSync14, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync13, readSync as readSync2, renameSync as renameSync11, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync13 } from "fs";
3434
+ import { tmpdir } from "os";
3435
+ import { join as join17 } from "path";
3436
+ function wakeDir(home = deskbuddyHome()) {
3437
+ return join17(home, "wake");
3438
+ }
3439
+ function marksDir(home = deskbuddyHome()) {
3440
+ return join17(wakeDir(home), "marks");
3441
+ }
3442
+ function safeSid(sessionId) {
3443
+ return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
3444
+ }
3445
+ function homeTag(home) {
3446
+ return createHash3("sha1").update(home).digest("hex").slice(0, 16);
3447
+ }
3448
+ function wakeChannelPath(platform = process.platform, home = deskbuddyHome()) {
3449
+ if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-wake-${homeTag(home)}`;
3450
+ const primary = join17(wakeDir(home), "wake.sock");
3451
+ if (Buffer.byteLength(primary) <= 103) return primary;
3452
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
3453
+ return join17(tmpdir(), `deskbuddy-${uid}`, `wake-${homeTag(home)}.sock`);
3454
+ }
3455
+ function pidExists(pid) {
3456
+ if (!Number.isInteger(pid) || pid <= 0) return false;
3457
+ try {
3458
+ process.kill(pid, 0);
3459
+ return true;
3460
+ } catch {
3461
+ return false;
3462
+ }
3463
+ }
3464
+ function writeWakeMark(sessionId, agentPid, now = Date.now()) {
3465
+ const safe = safeSid(sessionId);
3466
+ if (!safe) return;
3467
+ try {
3468
+ mkdirSync14(marksDir(), { recursive: true, mode: 448 });
3469
+ const file = join17(marksDir(), `${safe}.json`);
3470
+ const tmp = `${file}.${process.pid}.tmp`;
3471
+ writeFileSync13(tmp, JSON.stringify({ agentPid: agentPid ?? 0, daemonPid: process.pid, at: now }), { encoding: "utf8", mode: 384 });
3472
+ renameSync11(tmp, file);
3473
+ } catch {
3474
+ }
3475
+ }
3476
+ function removeWakeMark(sessionId) {
3477
+ const safe = safeSid(sessionId);
3478
+ if (!safe) return;
3479
+ try {
3480
+ rmSync6(join17(marksDir(), `${safe}.json`), { force: true });
3481
+ } catch {
3482
+ }
3483
+ }
3484
+ function clearWakeMarks() {
3485
+ let names = [];
3486
+ try {
3487
+ names = readdirSync3(marksDir());
3488
+ } catch {
3489
+ return;
3490
+ }
3491
+ for (const n of names) {
3492
+ try {
3493
+ rmSync6(join17(marksDir(), n), { force: true });
3494
+ } catch {
3495
+ }
3496
+ }
3497
+ }
3498
+ function isWakeMarked(sessionId, alive = pidExists) {
3499
+ const safe = safeSid(sessionId);
3500
+ if (!safe) return false;
3501
+ try {
3502
+ const m = JSON.parse(readFileSync13(join17(marksDir(), `${safe}.json`), "utf8"));
3503
+ const daemonPid = Number(m?.daemonPid) || 0;
3504
+ if (!daemonPid || !alive(daemonPid)) return false;
3505
+ const pid = Number(m?.agentPid) || 0;
3506
+ return pid > 0 ? alive(pid) : true;
3507
+ } catch {
3508
+ return false;
3509
+ }
3510
+ }
3511
+ function withWake(states, alive) {
3512
+ return states.map((s) => s.remoteWindow === true || !isWakeMarked(s.sessionId, alive) ? s : { ...s, remoteWindow: true });
3513
+ }
3514
+ function wakeSecretPath(home = deskbuddyHome()) {
3515
+ return join17(wakeDir(home), "secret");
3516
+ }
3517
+ function writeWakeSecret(hex, platform = process.platform) {
3518
+ const file = wakeSecretPath();
3519
+ mkdirSync14(wakeDir(), { recursive: true, mode: 448 });
3520
+ const tmp = `${file}.${process.pid}.tmp`;
3521
+ writeFileSync13(tmp, hex, { encoding: "utf8", mode: 384 });
3522
+ if (platform === "win32") {
3523
+ try {
3524
+ execFileSync7("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
3525
+ } catch {
3526
+ renameSync11(tmp, file);
3527
+ return "\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u6587\u4EF6\u7684\u6743\u9650\u6CA1\u80FD\u6536\u7D27\uFF08icacls \u5931\u8D25\uFF09";
3528
+ }
3529
+ } else {
3530
+ chmodSync2(tmp, 384);
3531
+ }
3532
+ renameSync11(tmp, file);
3533
+ return "";
3534
+ }
3535
+ function restrictToUser(file, platform = process.platform) {
3536
+ if (platform !== "win32") return true;
3537
+ try {
3538
+ execFileSync7("icacls", [file, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
3539
+ return true;
3540
+ } catch {
3541
+ return false;
3542
+ }
3543
+ }
3544
+ function currentUserSid() {
3545
+ try {
3546
+ const out = execFileSync7("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
3547
+ const m = /"(S-1-[0-9-]+)"/.exec(out);
3548
+ if (m) return `*${m[1]}`;
3549
+ } catch {
3550
+ }
3551
+ return String(process.env.USERNAME);
3552
+ }
3553
+ function canonicalJson(v) {
3554
+ if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
3555
+ if (v && typeof v === "object") {
3556
+ const o = v;
3557
+ return "{" + Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => JSON.stringify(k) + ":" + canonicalJson(o[k])).join(",") + "}";
3558
+ }
3559
+ return JSON.stringify(v === void 0 ? null : v);
3560
+ }
3561
+ function wakeMac(secret, ...parts) {
3562
+ return createHmac2("sha256", secret).update(parts.join("|"), "utf8").digest("hex");
3563
+ }
3564
+ var MAC_RE = /^[0-9a-f]{64}$/;
3565
+ function macEqual(a, b) {
3566
+ if (typeof a !== "string" || typeof b !== "string" || !MAC_RE.test(a) || !MAC_RE.test(b)) return false;
3567
+ return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
3568
+ }
3569
+ var WAKE_MARK_RE = /\[DeskBuddy#([0-9A-Za-z_-]{1,64})(?::[0-9a-f]{32})?\]/;
3570
+ var INTERRUPT_RE = /^\[Request interrupted by user/;
3571
+ var TRANSCRIPT_TAIL2 = 256 * 1024;
3572
+ function userText(content) {
3573
+ if (typeof content === "string") return content;
3574
+ if (!Array.isArray(content)) return "";
3575
+ return content.filter((x) => x && x.type === "text").map((x) => String(x.text ?? "")).join("\n");
3576
+ }
3577
+ function analyzeTranscript(file) {
3578
+ if (!file) return null;
3579
+ let fd;
3580
+ try {
3581
+ const size = statSync4(file).size;
3582
+ const len = Math.min(size, TRANSCRIPT_TAIL2);
3583
+ const buf = Buffer.alloc(len);
3584
+ fd = openSync2(file, "r");
3585
+ readSync2(fd, buf, 0, len, size - len);
3586
+ const lines2 = buf.toString("utf8").split("\n");
3587
+ let seg = false;
3588
+ let reply = null;
3589
+ let humanAfter = false;
3590
+ let interrupted = null;
3591
+ for (let i = lines2.length - 1; i >= 0; i--) {
3592
+ const line = lines2[i];
3593
+ if (line.indexOf('"assistant"') === -1 && line.indexOf('"user"') === -1) continue;
3594
+ let o;
3595
+ try {
3596
+ o = JSON.parse(line);
3597
+ } catch {
3598
+ continue;
3599
+ }
3600
+ if (!o || !o.message) continue;
3601
+ if (o.type === "assistant") {
3602
+ const t = userText(o.message.content).trim();
3603
+ if (t) {
3604
+ seg = true;
3605
+ if (reply === null) reply = t;
3606
+ }
3607
+ continue;
3608
+ }
3609
+ if (o.type !== "user") continue;
3610
+ const text = userText(o.message.content);
3611
+ if (!text) continue;
3612
+ const m = WAKE_MARK_RE.exec(text);
3613
+ if (!m && (o.isMeta === true || text.trim().startsWith("<"))) continue;
3614
+ if (m) return { id: m[1], answered: seg, humanAfter, interrupted: interrupted === true, reply: reply ?? "" };
3615
+ if (interrupted === null) interrupted = INTERRUPT_RE.test(text.trim());
3616
+ humanAfter = true;
3617
+ seg = false;
3618
+ reply = null;
3619
+ }
3620
+ return interrupted === null ? null : { id: "", answered: false, humanAfter, interrupted, reply: "" };
3621
+ } catch {
3622
+ return null;
3623
+ } finally {
3624
+ if (fd !== void 0) {
3625
+ try {
3626
+ closeSync2(fd);
3627
+ } catch {
3628
+ }
3629
+ }
3630
+ }
3631
+ }
3632
+
3633
+ // src/wrap/channel.ts
3281
3634
  var MAX_LINE_BYTES = 64 * 1024;
3282
3635
  function lines(sock, onLine) {
3283
3636
  let buf = "";
@@ -3298,46 +3651,81 @@ function lines(sock, onLine) {
3298
3651
  }
3299
3652
  function parseRequest(line) {
3300
3653
  let m;
3301
- try {
3302
- m = JSON.parse(line);
3303
- } catch {
3304
- return null;
3654
+ if (typeof line === "string") {
3655
+ try {
3656
+ m = JSON.parse(line);
3657
+ } catch {
3658
+ return null;
3659
+ }
3660
+ } else {
3661
+ m = line;
3305
3662
  }
3306
3663
  if (!m || typeof m !== "object") return null;
3307
3664
  const instructionId = typeof m.instructionId === "string" ? m.instructionId : "";
3308
3665
  const sessionId = typeof m.sessionId === "string" ? m.sessionId : "";
3309
- if (!isValidId(instructionId) || !isValidId(sessionId)) return null;
3666
+ if (!isValidId(instructionId) || sessionId !== "" && !isValidId(sessionId)) return null;
3310
3667
  const text = sanitizeText(typeof m.text === "string" ? m.text : "");
3311
3668
  if (!text || text.length > MAX_TEXT_LENGTH) return null;
3312
3669
  const expireAt = typeof m.expireAt === "number" && Number.isFinite(m.expireAt) ? m.expireAt : 0;
3313
3670
  return { instructionId, sessionId, text, expireAt };
3314
3671
  }
3315
- function startChannelServer(path, onRequest, platform = process.platform) {
3672
+ var HEX32 = /^[0-9a-f]{32}$/;
3673
+ function replyMac(secret, nonceC, r) {
3674
+ return wakeMac(secret, "r", nonceC, r.instructionId, r.status, r.reason ?? "");
3675
+ }
3676
+ function startChannelServer(path, secret, onRequest, platform = process.platform) {
3316
3677
  return new Promise((resolve5) => {
3317
3678
  const server = createServer2((sock) => {
3318
3679
  let open = true;
3680
+ let phase = "hello";
3681
+ let nonceC = "";
3682
+ let nonceS = "";
3319
3683
  sock.on("error", () => {
3320
3684
  });
3321
3685
  sock.on("close", () => open = false);
3322
- const reply = (r) => {
3686
+ const authTimer = setTimeout(() => {
3687
+ if (phase === "hello") sock.destroy();
3688
+ }, 5e3);
3689
+ authTimer.unref?.();
3690
+ const raw = (m) => {
3323
3691
  if (sock.destroyed || !sock.writable) return;
3324
3692
  try {
3325
- sock.write(JSON.stringify(r) + "\n");
3693
+ sock.write(JSON.stringify(m) + "\n");
3326
3694
  } catch {
3327
3695
  }
3328
3696
  };
3697
+ const reply = (r) => raw({ ...r, mac: replyMac(secret, nonceC, r) });
3329
3698
  lines(sock, (line) => {
3330
- const req2 = parseRequest(line);
3331
- if (!req2) {
3332
- reply({ instructionId: "", status: "rejected", reason: "bad_request" });
3333
- return;
3699
+ try {
3700
+ const m = JSON.parse(line);
3701
+ if (!m || typeof m !== "object") throw new Error("bad frame");
3702
+ if (phase === "hello") {
3703
+ if (m.t !== "hello" || typeof m.nonceC !== "string" || !HEX32.test(m.nonceC)) throw new Error("bad hello");
3704
+ nonceC = m.nonceC;
3705
+ nonceS = randomBytes5(16).toString("hex");
3706
+ phase = "req";
3707
+ clearTimeout(authTimer);
3708
+ raw({ t: "auth", nonceS, mac: wakeMac(secret, "s", nonceC, nonceS) });
3709
+ return;
3710
+ }
3711
+ if (phase !== "req") return;
3712
+ const { mac, ...body } = m;
3713
+ if (!macEqual(mac, wakeMac(secret, "c", nonceS, canonicalJson(body)))) throw new Error("bad mac");
3714
+ const req2 = parseRequest(body);
3715
+ if (!req2) {
3716
+ reply({ instructionId: typeof body.instructionId === "string" ? body.instructionId : "", status: "rejected", reason: "bad_request" });
3717
+ return;
3718
+ }
3719
+ phase = "done";
3720
+ onRequest(req2, reply, () => open);
3721
+ } catch {
3722
+ sock.destroy();
3334
3723
  }
3335
- onRequest(req2, reply, () => open);
3336
3724
  });
3337
3725
  });
3338
3726
  if (platform !== "win32") {
3339
3727
  try {
3340
- rmSync5(path, { force: true });
3728
+ rmSync7(path, { force: true });
3341
3729
  } catch {
3342
3730
  }
3343
3731
  }
@@ -3353,7 +3741,7 @@ function startChannelServer(path, onRequest, platform = process.platform) {
3353
3741
  restore();
3354
3742
  if (platform !== "win32") {
3355
3743
  try {
3356
- chmodSync2(path, 384);
3744
+ chmodSync3(path, 384);
3357
3745
  } catch {
3358
3746
  }
3359
3747
  }
@@ -3370,17 +3758,23 @@ function closeChannelServer(server, path, platform = process.platform) {
3370
3758
  }
3371
3759
  if (platform !== "win32") {
3372
3760
  try {
3373
- rmSync5(path, { force: true });
3761
+ rmSync7(path, { force: true });
3374
3762
  } catch {
3375
3763
  }
3376
3764
  }
3377
3765
  }
3378
3766
  var FINAL = /* @__PURE__ */ new Set(["started", "rejected", "failed", "not_here"]);
3379
- function sendToWindow(path, req2, onReply, opts = {}) {
3767
+ function sendToWindow(path, secret, req2, onReply, opts = {}) {
3380
3768
  return new Promise((resolve5) => {
3769
+ if (!secret) {
3770
+ resolve5("fallback");
3771
+ return;
3772
+ }
3381
3773
  let settled = false;
3382
3774
  let handled = false;
3383
3775
  let done = false;
3776
+ const nonceC = randomBytes5(16).toString("hex");
3777
+ let nonceS = "";
3384
3778
  const settle = (v) => {
3385
3779
  if (settled) return;
3386
3780
  settled = true;
@@ -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,85 @@ 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 PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
3877
+ function inspectLines(lines2) {
3878
+ const text = lines2.join("\n");
3879
+ const permission = PERMISSION_RE.test(text);
3880
+ const rules = [];
3881
+ lines2.forEach((l, i) => {
3882
+ if (/^─{20,}/.test(l.trim())) rules.push(i);
3883
+ });
3884
+ let input = null;
3885
+ if (rules.length >= 2) {
3886
+ const a = rules[rules.length - 2];
3887
+ const b = rules[rules.length - 1];
3888
+ input = lines2.slice(a + 1, b).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
3889
+ }
3890
+ return { permission, input };
3891
+ }
3892
+ function boxMatches(view, body) {
3893
+ if (view.input === null || view.permission) return false;
3894
+ const flat = (x) => x.replace(/\s+/g, "");
3895
+ return flat(view.input) === flat(body) || /\[Pasted text #\d+/.test(view.input);
3896
+ }
3897
+
3447
3898
  // 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";
3899
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync15, readdirSync as readdirSync4, readFileSync as readFileSync14, renameSync as renameSync12, rmSync as rmSync8, statSync as statSync5, writeFileSync as writeFileSync14 } from "fs";
3900
+ import { tmpdir as tmpdir2 } from "os";
3901
+ import { join as join18 } from "path";
3451
3902
  var WINDOW_ID_RE = /^[a-f0-9]{32}$/;
3452
3903
  function windowsDir() {
3453
- return join16(deskbuddyHome(), "windows");
3904
+ return join18(deskbuddyHome(), "windows");
3454
3905
  }
3455
3906
  function bySessionDir() {
3456
- return join16(windowsDir(), "by-session");
3907
+ return join18(windowsDir(), "by-session");
3457
3908
  }
3458
- function safeSid(sessionId) {
3909
+ function safeSid2(sessionId) {
3459
3910
  return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
3460
3911
  }
3461
3912
  function ensureDir(dir) {
3462
- mkdirSync13(dir, { recursive: true, mode: 448 });
3913
+ mkdirSync15(dir, { recursive: true, mode: 448 });
3463
3914
  }
3464
3915
  function atomicWrite(file, text) {
3465
3916
  const tmp = `${file}.${process.pid}.tmp`;
3466
- writeFileSync12(tmp, text, { encoding: "utf8", mode: 384 });
3467
- renameSync10(tmp, file);
3917
+ writeFileSync14(tmp, text, { encoding: "utf8", mode: 384 });
3918
+ renameSync12(tmp, file);
3468
3919
  }
3469
3920
  function windowFile(windowId) {
3470
- return join16(windowsDir(), `${windowId}.json`);
3921
+ return join18(windowsDir(), `${windowId}.json`);
3471
3922
  }
3472
3923
  function writeWindow(windowId, rec) {
3473
3924
  if (!WINDOW_ID_RE.test(windowId)) return;
3474
3925
  ensureDir(windowsDir());
3475
3926
  atomicWrite(windowFile(windowId), JSON.stringify(rec));
3927
+ restrictToUser(windowFile(windowId));
3928
+ }
3929
+ function windowSecret(windowId) {
3930
+ const s = readWindow(windowId)?.secret;
3931
+ return typeof s === "string" && /^[0-9a-f]{64}$/.test(s) ? Buffer.from(s, "hex") : null;
3476
3932
  }
3477
3933
  function readWindow(windowId) {
3478
3934
  if (!WINDOW_ID_RE.test(windowId)) return null;
3479
3935
  try {
3480
- const r = JSON.parse(readFileSync12(windowFile(windowId), "utf8"));
3936
+ const r = JSON.parse(readFileSync14(windowFile(windowId), "utf8"));
3481
3937
  return r && typeof r.pid === "number" ? r : null;
3482
3938
  } catch {
3483
3939
  return null;
@@ -3487,41 +3943,41 @@ function removeWindow(windowId) {
3487
3943
  if (!WINDOW_ID_RE.test(windowId)) return;
3488
3944
  for (const f of [windowFile(windowId), lastEventFile(windowId)]) {
3489
3945
  try {
3490
- rmSync6(f, { force: true });
3946
+ rmSync8(f, { force: true });
3491
3947
  } catch {
3492
3948
  }
3493
3949
  }
3494
3950
  let names = [];
3495
3951
  try {
3496
- names = readdirSync2(bySessionDir());
3952
+ names = readdirSync4(bySessionDir());
3497
3953
  } catch {
3498
3954
  return;
3499
3955
  }
3500
3956
  for (const n of names) {
3501
- const f = join16(bySessionDir(), n);
3957
+ const f = join18(bySessionDir(), n);
3502
3958
  try {
3503
- if (readFileSync12(f, "utf8").trim() === windowId) rmSync6(f, { force: true });
3959
+ if (readFileSync14(f, "utf8").trim() === windowId) rmSync8(f, { force: true });
3504
3960
  } catch {
3505
3961
  }
3506
3962
  }
3507
3963
  }
3508
3964
  function readBySession(sessionId) {
3509
- const safe = safeSid(sessionId);
3965
+ const safe = safeSid2(sessionId);
3510
3966
  if (!safe) return null;
3511
3967
  try {
3512
- const id = readFileSync12(join16(bySessionDir(), safe), "utf8").trim();
3968
+ const id = readFileSync14(join18(bySessionDir(), safe), "utf8").trim();
3513
3969
  return WINDOW_ID_RE.test(id) ? id : null;
3514
3970
  } catch {
3515
3971
  return null;
3516
3972
  }
3517
3973
  }
3518
3974
  function lastEventFile(windowId) {
3519
- return join16(windowsDir(), `${windowId}.last`);
3975
+ return join18(windowsDir(), `${windowId}.last`);
3520
3976
  }
3521
3977
  function readLastEvent(windowId) {
3522
3978
  if (!WINDOW_ID_RE.test(windowId)) return null;
3523
3979
  try {
3524
- const e = JSON.parse(readFileSync12(lastEventFile(windowId), "utf8"));
3980
+ const e = JSON.parse(readFileSync14(lastEventFile(windowId), "utf8"));
3525
3981
  return e && typeof e.ev === "string" && typeof e.at === "number" ? { ev: e.ev, sid: String(e.sid ?? ""), at: e.at } : null;
3526
3982
  } catch {
3527
3983
  return null;
@@ -3530,18 +3986,18 @@ function readLastEvent(windowId) {
3530
3986
  function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
3531
3987
  if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
3532
3988
  const name = `${windowId.slice(0, 16)}.sock`;
3533
- const primary = join16(home, "win", name);
3989
+ const primary = join18(home, "win", name);
3534
3990
  if (Buffer.byteLength(primary) <= 103) return primary;
3535
3991
  const uid = typeof process.getuid === "function" ? process.getuid() : 0;
3536
- return join16(tmpdir(), `deskbuddy-${uid}`, name);
3992
+ return join18(tmpdir2(), `deskbuddy-${uid}`, name);
3537
3993
  }
3538
3994
  function ensureChannelDir(sockPath) {
3539
- const dir = join16(sockPath, "..");
3995
+ const dir = join18(sockPath, "..");
3540
3996
  try {
3541
- mkdirSync13(dir, { recursive: true, mode: 448 });
3542
- const st = statSync3(dir);
3997
+ mkdirSync15(dir, { recursive: true, mode: 448 });
3998
+ const st = statSync5(dir);
3543
3999
  if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
3544
- chmodSync3(dir, 448);
4000
+ chmodSync4(dir, 448);
3545
4001
  return true;
3546
4002
  } catch {
3547
4003
  return false;
@@ -3557,203 +4013,189 @@ function windowForSession(sessionId) {
3557
4013
  return id;
3558
4014
  }
3559
4015
 
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 = [];
4016
+ // src/core/launch.ts
4017
+ import { mkdirSync as mkdirSync16, readFileSync as readFileSync15, renameSync as renameSync13, rmSync as rmSync9, writeFileSync as writeFileSync15 } from "fs";
4018
+ import { join as join19 } from "path";
4019
+ var RUN_ID_RE = /^[a-f0-9]{32}$/;
4020
+ var SESSION_UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
4021
+ function launchDir(home = deskbuddyHome()) {
4022
+ return join19(home, "launch");
4023
+ }
4024
+ function planFile(runId) {
4025
+ return join19(launchDir(), `${runId}.json`);
4026
+ }
4027
+ function writeLaunch(runId, plan) {
4028
+ if (!RUN_ID_RE.test(runId)) throw new Error("bad runId");
4029
+ if (plan.resume && !SESSION_UUID_RE.test(plan.resume)) throw new Error("bad session id");
4030
+ mkdirSync16(launchDir(), { recursive: true, mode: 448 });
4031
+ const tmp = `${planFile(runId)}.${process.pid}.tmp`;
4032
+ writeFileSync15(tmp, JSON.stringify(plan), { encoding: "utf8", mode: 384 });
4033
+ restrictToUser(tmp);
4034
+ renameSync13(tmp, planFile(runId));
4035
+ }
4036
+ function claimLaunch(runId) {
4037
+ if (!RUN_ID_RE.test(runId)) return null;
4038
+ const claimed = join19(launchDir(), `${runId}.${process.pid}.claimed`);
3616
4039
  try {
3617
- names = readdirSync3(marksDir());
4040
+ renameSync13(planFile(runId), claimed);
3618
4041
  } catch {
3619
- return;
3620
- }
3621
- for (const n of names) {
3622
- try {
3623
- rmSync7(join17(marksDir(), n), { force: true });
3624
- } catch {
3625
- }
4042
+ return null;
3626
4043
  }
3627
- }
3628
- function isWakeMarked(sessionId, alive = pidExists) {
3629
- const safe = safeSid2(sessionId);
3630
- if (!safe) return false;
3631
4044
  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;
4045
+ const p = JSON.parse(readFileSync15(claimed, "utf8"));
4046
+ if (!p || typeof p.cwd !== "string" || typeof p.resume !== "string") return null;
4047
+ if (p.resume && !SESSION_UUID_RE.test(p.resume)) return null;
4048
+ return p;
3637
4049
  } 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") {
4050
+ return null;
4051
+ } finally {
3653
4052
  try {
3654
- execFileSync7("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
4053
+ rmSync9(claimed, { force: true });
3655
4054
  } 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
4055
  }
3659
- } else {
3660
- chmodSync4(tmp, 384);
3661
4056
  }
3662
- renameSync11(tmp, file);
3663
- return "";
3664
4057
  }
3665
- function currentUserSid() {
4058
+ function launchPending(runId) {
3666
4059
  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(",") + "}";
4060
+ readFileSync15(planFile(runId));
4061
+ return true;
4062
+ } catch {
4063
+ return false;
3679
4064
  }
3680
- return JSON.stringify(v === void 0 ? null : v);
3681
4065
  }
3682
- function wakeMac(secret, ...parts) {
3683
- return createHmac2("sha256", secret).update(parts.join("|"), "utf8").digest("hex");
4066
+
4067
+ // src/daemon/openWindow.ts
4068
+ import { execFile as execFile3, execFileSync as execFileSync8, spawn as spawn2 } from "child_process";
4069
+ import { chmodSync as chmodSync5, mkdirSync as mkdirSync17, rmSync as rmSync10, writeFileSync as writeFileSync16 } from "fs";
4070
+ import { userInfo } from "os";
4071
+ import { join as join20 } from "path";
4072
+ function safePath(p) {
4073
+ return /^(?:[A-Za-z]:[\\/]|\/)/.test(p) && !/[\r\n\0"'`$%;]/.test(p);
4074
+ }
4075
+ function checkTarget(t) {
4076
+ if (!RUN_ID_RE.test(t.runId)) throw new Error("bad runId");
4077
+ for (const p of [t.node, t.cli, t.home]) if (!safePath(p)) throw new Error("\u8DEF\u5F84\u91CC\u6709\u4E0D\u80FD\u8FDB\u547D\u4EE4\u884C\u7684\u5B57\u7B26");
4078
+ }
4079
+ var shq = (s) => `'${s}'`;
4080
+ function macCommandScript(t) {
4081
+ checkTarget(t);
4082
+ return `#!/bin/sh
4083
+ exec ${shq(t.node)} ${shq(t.cli)} wrap --launch ${t.runId} --home ${shq(t.home)}
4084
+ `;
3684
4085
  }
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"));
4086
+ function winWtArgs(t) {
4087
+ checkTarget(t);
4088
+ return ["-w", "new", t.node, t.cli, "wrap", "--launch", t.runId, "--home", t.home];
3689
4089
  }
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");
4090
+ var psq = (s) => `'${s.replace(/'/g, "''")}'`;
4091
+ function winStartProcessScript(t) {
4092
+ checkTarget(t);
4093
+ const arg = (s) => psq(/\s/.test(s) ? `"${s}"` : s);
4094
+ return `Start-Process -FilePath ${psq(t.node)} -ArgumentList @(${[t.cli, "wrap", "--launch", t.runId, "--home", t.home].map(arg).join(",")})`;
3697
4095
  }
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;
4096
+ function encodePs(script) {
4097
+ return Buffer.from(script, "utf16le").toString("base64");
4098
+ }
4099
+ function guiAvailable(d) {
4100
+ if (d.env.SSH_CONNECTION || d.env.SSH_TTY) return false;
4101
+ if (d.platform === "darwin") {
4102
+ const m = d.macManager().trim();
4103
+ if (m === "Aqua") return true;
4104
+ if (m !== "Background") return false;
4105
+ const me = d.userName?.() ?? "";
4106
+ return !!me && (d.macConsoleUser?.() ?? "").trim() === me;
4107
+ }
4108
+ if (d.platform === "win32") {
4109
+ const s = d.winSessionId();
4110
+ return s !== null && s > 0;
4111
+ }
4112
+ return false;
4113
+ }
4114
+ var defaultGuiDeps = () => ({
4115
+ platform: process.platform,
4116
+ env: process.env,
4117
+ macManager: () => {
4118
+ try {
4119
+ return execFileSync8("launchctl", ["managername"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
4120
+ } catch {
4121
+ return "";
3740
4122
  }
3741
- return interrupted === null ? null : { id: "", answered: false, humanAfter, interrupted, reply: "" };
3742
- } catch {
3743
- return null;
3744
- } finally {
3745
- if (fd !== void 0) {
3746
- try {
3747
- closeSync(fd);
3748
- } catch {
3749
- }
4123
+ },
4124
+ macConsoleUser: () => {
4125
+ try {
4126
+ return execFileSync8("stat", ["-f", "%Su", "/dev/console"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
4127
+ } catch {
4128
+ return "";
4129
+ }
4130
+ },
4131
+ userName: () => {
4132
+ try {
4133
+ return userInfo().username;
4134
+ } catch {
4135
+ return "";
4136
+ }
4137
+ },
4138
+ winSessionId: () => {
4139
+ try {
4140
+ const out = execFileSync8("tasklist", ["/FI", `PID eq ${process.pid}`, "/FO", "CSV", "/NH"], {
4141
+ encoding: "utf8",
4142
+ stdio: ["ignore", "pipe", "ignore"],
4143
+ windowsHide: true,
4144
+ timeout: 5e3
4145
+ });
4146
+ const cols = out.trim().split("\n")[0]?.match(/"([^"]*)"/g)?.map((c) => c.slice(1, -1)) ?? [];
4147
+ const n = Number(cols[3]);
4148
+ return Number.isInteger(n) ? n : null;
4149
+ } catch {
4150
+ return null;
3750
4151
  }
3751
4152
  }
4153
+ });
4154
+ var defaultOpenerDeps = (findWt2) => ({
4155
+ platform: process.platform,
4156
+ findWt: findWt2,
4157
+ run: (file, args) => new Promise((resolve5, reject) => {
4158
+ execFile3(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) : resolve5());
4159
+ }),
4160
+ spawnDetached: (file, args) => {
4161
+ const c = spawn2(file, args, { detached: true, stdio: "ignore", shell: false, windowsHide: false });
4162
+ c.on("error", () => {
4163
+ });
4164
+ c.unref();
4165
+ }
4166
+ });
4167
+ async function openTerminal(t, d) {
4168
+ checkTarget(t);
4169
+ if (d.platform === "darwin") {
4170
+ mkdirSync17(launchDir(t.home), { recursive: true, mode: 448 });
4171
+ const file = join20(launchDir(t.home), `${t.runId}.command`);
4172
+ writeFileSync16(file, macCommandScript(t), { encoding: "utf8", mode: 448 });
4173
+ chmodSync5(file, 448);
4174
+ await d.run("open", ["-a", "Terminal", file]);
4175
+ return "terminal";
4176
+ }
4177
+ if (d.platform === "win32") {
4178
+ const wt = d.findWt();
4179
+ if (wt) {
4180
+ d.spawnDetached(wt, winWtArgs(t));
4181
+ return "wt";
4182
+ }
4183
+ await d.run("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", encodePs(winStartProcessScript(t))]);
4184
+ return "powershell";
4185
+ }
4186
+ throw new Error(`\u6682\u4E0D\u652F\u6301\u5728 ${d.platform} \u4E0A\u5F00\u7A97`);
4187
+ }
4188
+ function removeCommandFile(home, runId) {
4189
+ if (!RUN_ID_RE.test(runId)) return;
4190
+ try {
4191
+ rmSync10(join20(launchDir(home), `${runId}.command`), { force: true });
4192
+ } catch {
4193
+ }
3752
4194
  }
3753
4195
 
3754
4196
  // src/daemon/wake.ts
3755
- import { randomBytes as randomBytes5 } from "crypto";
3756
- import { rmSync as rmSync8 } from "fs";
4197
+ import { randomBytes as randomBytes6 } from "crypto";
4198
+ import { rmSync as rmSync11 } from "fs";
3757
4199
  import { createServer as createServer3 } from "net";
3758
4200
  var BUSY_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
3759
4201
  var STOP_MIN_MS = 1500;
@@ -4040,7 +4482,7 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
4040
4482
  if (platform !== "win32") {
4041
4483
  if (!ensureChannelDir(path)) return Promise.resolve(null);
4042
4484
  try {
4043
- rmSync8(path, { force: true });
4485
+ rmSync11(path, { force: true });
4044
4486
  } catch {
4045
4487
  }
4046
4488
  }
@@ -4092,7 +4534,7 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
4092
4534
  return;
4093
4535
  }
4094
4536
  nonceW = m.nonceW;
4095
- nonceD = randomBytes5(16).toString("hex");
4537
+ nonceD = randomBytes6(16).toString("hex");
4096
4538
  phase = "auth2";
4097
4539
  raw({ t: "auth", nonceD, mac: wakeMac(secret, "d", nonceW, nonceD) });
4098
4540
  return;
@@ -4147,6 +4589,8 @@ var POLICY_VIOLATION = 1008;
4147
4589
  var PING_FRAME_SERVICE = "ping";
4148
4590
  var PONG_FRAME = JSON.stringify({ service: "pong", version: "1.0" });
4149
4591
  var CODEX_REMOTE_REFRESH_MS = 30 * 1e3;
4592
+ var CLAIM_WAIT_MS = 15 * 1e3;
4593
+ var WINDOW_READY_MS = 30 * 1e3;
4150
4594
  var CODEX_WATCH_MS = 5 * 1e3;
4151
4595
  function defaultCodexAppServers() {
4152
4596
  const cfg = loadConfig();
@@ -4176,6 +4620,11 @@ var Daemon = class {
4176
4620
  #aliveReport = null;
4177
4621
  #codexTimer = null;
4178
4622
  #wake;
4623
+ /**
4624
+ * 会话号 → 这个会话正在进行的开窗流程(开窗、等认领、交首句;降级后台时一直到后台那次跑完)。
4625
+ * 🔴 同一会话不能被两个进程同时续跑(实测会静默丢一整轮):后到的指令等它结束再路由
4626
+ */
4627
+ #opening = /* @__PURE__ */ new Map();
4179
4628
  #wakeServer = null;
4180
4629
  #wakeTimer = null;
4181
4630
  #wakeReport = null;
@@ -4199,7 +4648,16 @@ var Daemon = class {
4199
4648
  codexRoute: deps.codexRoute ?? ((tid) => this.#codexRouteFor(tid)),
4200
4649
  report: deps.report ?? ((p) => api.commandResult(p)),
4201
4650
  windowFor: deps.windowFor ?? windowForSession,
4202
- sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), req2, onReply)),
4651
+ sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), windowSecret(id), req2, onReply)),
4652
+ gui: deps.gui ?? (() => guiAvailable(defaultGuiDeps())),
4653
+ openWindow: deps.openWindow ?? ((runId) => this.#openTerminal(runId)),
4654
+ endedCwd: deps.endedCwd ?? ((sid, tool, stateCwd) => resolveEndedCwd(sid, tool, {
4655
+ readEnded: (s) => readEnded(s) ?? (stateCwd ? { sessionId: s, tool, cwd: stateCwd, interactive: false, endedAt: 0 } : null),
4656
+ transcriptCwd: (s) => claudeTranscriptCwd(s),
4657
+ isDir: isDirSync
4658
+ })),
4659
+ claimWaitMs: deps.claimWaitMs ?? CLAIM_WAIT_MS,
4660
+ windowReadyMs: deps.windowReadyMs ?? WINDOW_READY_MS,
4203
4661
  wakePath: deps.wakePath === void 0 ? wakeChannelPath() : deps.wakePath,
4204
4662
  onWindowDone: deps.onWindowDone ?? (() => {
4205
4663
  })
@@ -4253,7 +4711,7 @@ var Daemon = class {
4253
4711
  const path = this.#deps.wakePath;
4254
4712
  if (!path) return;
4255
4713
  clearWakeMarks();
4256
- const secret = randomBytes6(32);
4714
+ const secret = randomBytes7(32);
4257
4715
  let warn = "";
4258
4716
  try {
4259
4717
  warn = writeWakeSecret(secret.toString("hex"));
@@ -4515,6 +4973,22 @@ var Daemon = class {
4515
4973
  return;
4516
4974
  }
4517
4975
  this.#seen.save();
4976
+ this.#route(cmd);
4977
+ }
4978
+ /**
4979
+ * 指令分派(去重之后)。同一会话的开窗流程还没走完时,后到的指令等它结束再从头路由一遍
4980
+ * (届时窗口已登记 → 走窗口写入;降级后台了 → 按原来的按会话串行排队)
4981
+ */
4982
+ #route(cmd) {
4983
+ if (cmd.tool === "claude_code" && cmd.sessionId) {
4984
+ const sid = resolveAlias(cmd.sessionId);
4985
+ const pending = this.#opening.get(sid);
4986
+ if (pending) {
4987
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u7B49\u540C\u4E00\u4F1A\u8BDD\u7684\u5F00\u7A97\u6D41\u7A0B\u7ED3\u675F\u540E\u518D\u5206\u6D3E`);
4988
+ void pending.then(() => this.#route(cmd));
4989
+ return;
4990
+ }
4991
+ }
4518
4992
  if (cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
4519
4993
  const sid = resolveAlias(cmd.sessionId);
4520
4994
  let win = null;
@@ -4556,8 +5030,197 @@ var Daemon = class {
4556
5030
  const sid = resolveAlias(cmd.sessionId);
4557
5031
  if (this.#wake.submit(cmd, sid)) return;
4558
5032
  }
5033
+ if (cmd.tool === "claude_code" && !isExpired(cmd, this.#deps.now())) {
5034
+ void this.#openOrDispatch(cmd).catch((e) => {
5035
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u5F00\u7A97\u6D41\u7A0B\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
5036
+ this.#report(cmd, cmd.sessionId, "failed", "", "window_failed");
5037
+ });
5038
+ return;
5039
+ }
5040
+ if (cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
5041
+ const sid = resolveAlias(cmd.sessionId);
5042
+ if (!readState(sid) && readEnded(sid)?.tool === cmd.tool) {
5043
+ const r = this.#deps.endedCwd(sid, cmd.tool, "");
5044
+ if (!r.ok) {
5045
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\u4F46${r.reason === "no_cwd" ? "\u53D6\u4E0D\u5230\u53EF\u9760\u7684\u5DE5\u4F5C\u76EE\u5F55" : "\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5728"}\uFF0C\u4E0D\u6267\u884C`);
5046
+ this.#report(cmd, sid, "rejected", "", r.reason);
5047
+ return;
5048
+ }
5049
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\uFF0C\u540E\u53F0\u7EED\u8DD1`);
5050
+ void this.#headless(cmd, sid, r.cwd);
5051
+ return;
5052
+ }
5053
+ }
4559
5054
  this.#dispatch(cmd);
4560
5055
  }
5056
+ /**
5057
+ * 新建任务、会话已结束 → 开窗;会话还活着(后台 -p 在跑、或在用户自己开的窗口里)→ 原来的路径(排队 / window_open)
5058
+ */
5059
+ async #openOrDispatch(cmd) {
5060
+ if (tooLongToInject(cmd.text)) {
5061
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u592A\u957F\uFF08${cmd.text.length} \u5B57\uFF09\uFF0C\u4E0D\u6267\u884C`);
5062
+ this.#report(cmd, cmd.sessionId ? resolveAlias(cmd.sessionId) : "", "rejected", "", TEXT_TOO_LONG);
5063
+ return;
5064
+ }
5065
+ if (!cmd.sessionId) {
5066
+ let cwd = "";
5067
+ if (cmd.dirRef) {
5068
+ cwd = dirOf(cmd.dirRef);
5069
+ if (!cwd) {
5070
+ this.#report(cmd, "", "rejected", "", "dir_gone");
5071
+ return;
5072
+ }
5073
+ } else {
5074
+ cwd = loadConfig().remoteCwd || "";
5075
+ if (!cwd) {
5076
+ this.#report(cmd, "", "rejected", "", "no_remote_cwd");
5077
+ return;
5078
+ }
5079
+ }
5080
+ await this.#openWindowFor(cmd, "", cwd);
5081
+ return;
5082
+ }
5083
+ const sid = resolveAlias(cmd.sessionId);
5084
+ for (let p = this.#opening.get(sid); p; p = this.#opening.get(sid)) await p;
5085
+ let release2;
5086
+ this.#opening.set(sid, new Promise((r) => release2 = r));
5087
+ try {
5088
+ if (this.#queue.busy(sid)) await this.#queue.enqueue(sid, async () => {
5089
+ });
5090
+ const st = readState(sid);
5091
+ if (st && (!st.pid || isAlive(st.pid, st.pidName))) {
5092
+ this.#dispatch(cmd);
5093
+ return;
5094
+ }
5095
+ if (!SESSION_UUID_RE.test(sid)) {
5096
+ this.#dispatch(cmd);
5097
+ return;
5098
+ }
5099
+ const r = this.#deps.endedCwd(sid, cmd.tool, st?.cwd ?? "");
5100
+ if (!r.ok) {
5101
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\u4F46${r.reason === "no_cwd" ? "\u53D6\u4E0D\u5230\u53EF\u9760\u7684\u5DE5\u4F5C\u76EE\u5F55" : "\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5728"}\uFF0C\u4E0D\u6267\u884C`);
5102
+ this.#report(cmd, sid, "rejected", "", r.reason);
5103
+ return;
5104
+ }
5105
+ await this.#openWindowFor(cmd, sid, r.cwd);
5106
+ } finally {
5107
+ this.#opening.delete(sid);
5108
+ release2();
5109
+ }
5110
+ }
5111
+ /**
5112
+ * 开窗 → 等认领 → 首句经通道交给窗口;任何一步起不来 → 后台 -p(回报 started/headless)。
5113
+ * 返回时:窗口已收下首句 / 已按失败回报 / 降级的后台那次已跑完
5114
+ */
5115
+ async #openWindowFor(cmd, sid, cwd) {
5116
+ const headless = (why) => {
5117
+ log(`\u6307\u4EE4 ${cmd.instructionId} ${why}\uFF0C\u6539\u540E\u53F0\u6267\u884C`);
5118
+ return this.#headless(cmd, sid, cwd);
5119
+ };
5120
+ let gui = false;
5121
+ try {
5122
+ gui = this.#deps.gui();
5123
+ } catch {
5124
+ gui = false;
5125
+ }
5126
+ if (!gui) return headless("\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\uFF08SSH / \u9501\u5B9A\u7684\u670D\u52A1\u4F1A\u8BDD / Linux\uFF09");
5127
+ const runId = randomBytes7(16).toString("hex");
5128
+ try {
5129
+ writeLaunch(runId, { tool: "claude_code", cwd, resume: sid, at: this.#deps.now() });
5130
+ } catch (e) {
5131
+ return headless(`\u5199\u4EA4\u63A5\u5355\u5931\u8D25\uFF1A${e.message}`);
5132
+ }
5133
+ try {
5134
+ await this.#deps.openWindow(runId);
5135
+ } catch (e) {
5136
+ if (claimLaunch(runId)) {
5137
+ removeCommandFile(deskbuddyHome(), runId);
5138
+ return headless(`\u5F00\u7A97\u5931\u8D25\uFF1A${e.message}`);
5139
+ }
5140
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u5F00\u7A97\u547D\u4EE4\u62A5\u9519\uFF08${e.message}\uFF09\uFF0C\u4F46\u7A97\u53E3\u5DF2\u8BA4\u9886\uFF0C\u6309\u7A97\u53E3\u7EE7\u7EED`);
5141
+ }
5142
+ const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
5143
+ const claimDeadline = this.#deps.now() + this.#deps.claimWaitMs;
5144
+ while (launchPending(runId) && this.#deps.now() < claimDeadline) await sleep2(300);
5145
+ removeCommandFile(deskbuddyHome(), runId);
5146
+ if (launchPending(runId) && claimLaunch(runId)) return headless(`${Math.round(this.#deps.claimWaitMs / 1e3)} \u79D2\u5185\u7A97\u53E3\u6CA1\u8D77\u6765`);
5147
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u7A97\u53E3\u5DF2\u8D77\u6765\uFF08${runId.slice(0, 8)}\uFF09\uFF0C\u7B49\u5B83\u5C31\u7EEA\u540E\u5199\u5165\u9996\u53E5`);
5148
+ const req2 = { instructionId: cmd.instructionId, sessionId: "", text: cmd.text, expireAt: cmd.expireAt };
5149
+ const fb = { p: null, final: false };
5150
+ const readyDeadline = this.#deps.now() + this.#deps.windowReadyMs;
5151
+ while (this.#deps.now() < readyDeadline) {
5152
+ let r = "fallback";
5153
+ try {
5154
+ r = await this.#deps.sendToWindow(runId, req2, (reply) => {
5155
+ if (reply.status === "ack") return;
5156
+ if (reply.status !== "queued") fb.final = true;
5157
+ if (reply.status === "not_here") {
5158
+ if (!fb.p) fb.p = headless("\u9996\u53E5\u5199\u5165\u524D\u7A97\u53E3\u5DF2\u5173\u95ED");
5159
+ return;
5160
+ }
5161
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u7A97\u53E3 ${reply.status}${reply.reason ? " " + reply.reason : ""}`);
5162
+ this.#report(cmd, sid, reply.status, "window", reply.reason ?? "");
5163
+ });
5164
+ } catch {
5165
+ r = "fallback";
5166
+ }
5167
+ if (r === "handled") {
5168
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u9996\u53E5\u5DF2\u4EA4\u7ED9\u65B0\u7A97\u53E3 len=${cmd.text.length}`);
5169
+ if (sid) {
5170
+ const left = cmd.expireAt > 0 ? Math.max(cmd.expireAt - this.#deps.now(), 0) : 10 * 60 * 1e3;
5171
+ const cap = this.#deps.now() + left + this.#deps.windowReadyMs;
5172
+ while (!fb.final && this.#deps.now() < cap) {
5173
+ let found = null;
5174
+ try {
5175
+ found = this.#deps.windowFor(sid);
5176
+ } catch {
5177
+ found = null;
5178
+ }
5179
+ if (found) break;
5180
+ await sleep2(200);
5181
+ }
5182
+ }
5183
+ await sleep2(50);
5184
+ if (fb.p) await fb.p;
5185
+ return;
5186
+ }
5187
+ await sleep2(1e3);
5188
+ }
5189
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u65B0\u7A97\u53E3\u7684\u901A\u9053\u4E00\u76F4\u6CA1\u5C31\u7EEA`);
5190
+ this.#report(cmd, sid, "failed", "window", "window_failed");
5191
+ }
5192
+ /**
5193
+ * 后台 -p:新建任务走原来的路径;续跑已结束会话用解析出的 cwd,不再查状态文件(它已被删)。
5194
+ * 返回的 promise 在后台那次跑完时落定(开窗流程的会话锁要一直占到那时)
5195
+ */
5196
+ #headless(cmd, sid, cwd) {
5197
+ if (isExpired(cmd, this.#deps.now())) {
5198
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u5DF2\u8FC7\u671F\uFF0C\u4E0D\u518D\u540E\u53F0\u6267\u884C`);
5199
+ this.#report(cmd, sid, "rejected", "", "expired");
5200
+ return Promise.resolve();
5201
+ }
5202
+ if (!sid) {
5203
+ this.#dispatch(cmd);
5204
+ return Promise.resolve();
5205
+ }
5206
+ return this.#queue.enqueue(
5207
+ sid,
5208
+ () => this.#execute(cmd, sid, cwd, cmd.tool, true),
5209
+ (e) => {
5210
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u65F6\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
5211
+ this.#report(cmd, sid, "failed", "", "exec_failed");
5212
+ }
5213
+ );
5214
+ }
5215
+ /** 真的弹窗口(测试环境绝不弹:vitest 下直接当开窗失败) */
5216
+ async #openTerminal(runId) {
5217
+ if (process.env.VITEST) throw new Error("\u6D4B\u8BD5\u73AF\u5883\u4E0D\u5F00\u7A97");
5218
+ const how = await openTerminal(
5219
+ { node: this.#deps.nodePath, cli: defaultCliPath(), home: deskbuddyHome(), runId },
5220
+ defaultOpenerDeps(() => findWt())
5221
+ );
5222
+ log(`\u5F00\u7A97 ${runId.slice(0, 8)} \u65B9\u5F0F=${how}`);
5223
+ }
4561
5224
  /**
4562
5225
  * 经 app-server 往 Codex 会话里发一轮(切片 B)。回报语义与 Claude 窗口一致(mode=window):
4563
5226
  * 排队 queued → 发出 started;排队中过期 rejected/expired。
@@ -4685,8 +5348,8 @@ var Daemon = class {
4685
5348
  }
4686
5349
  );
4687
5350
  }
4688
- async #execute(cmd, sessionId, cwd, handleKey = cmd.tool) {
4689
- if (sessionId) {
5351
+ async #execute(cmd, sessionId, cwd, handleKey = cmd.tool, ended = false) {
5352
+ if (sessionId && !ended) {
4690
5353
  const fresh = readState(sessionId);
4691
5354
  if (!fresh) {
4692
5355
  log(`\u6307\u4EE4 ${cmd.instructionId} \u653E\u5F03\uFF1A\u4F1A\u8BDD ${sessionId} \u5728\u6267\u884C\u524D\u5DF2\u6D88\u5931`);
@@ -4810,7 +5473,7 @@ var Daemon = class {
4810
5473
  }
4811
5474
  };
4812
5475
  function newRunId() {
4813
- return randomBytes6(12).toString("hex");
5476
+ return randomBytes7(12).toString("hex");
4814
5477
  }
4815
5478
  async function runDaemon(deps = {}) {
4816
5479
  const d = new Daemon(deps);
@@ -4828,7 +5491,7 @@ async function runDaemonCommand(opts = {}) {
4828
5491
  }
4829
5492
  if (opts.foreground) process.env.DESKBUDDY_DAEMON_FOREGROUND = "1";
4830
5493
  try {
4831
- writeFileSync14(pidFilePath(), String(process.pid), "utf8");
5494
+ writeFileSync17(pidFilePath(), String(process.pid), "utf8");
4832
5495
  } catch {
4833
5496
  }
4834
5497
  let daemon;
@@ -4844,7 +5507,7 @@ async function runDaemonCommand(opts = {}) {
4844
5507
  log(`\u6536\u5230 ${sig}\uFF0C\u9000\u51FA`);
4845
5508
  daemon.stop();
4846
5509
  try {
4847
- rmSync9(pidFilePath(), { force: true });
5510
+ rmSync12(pidFilePath(), { force: true });
4848
5511
  } catch {
4849
5512
  }
4850
5513
  process.exit(0);
@@ -4861,8 +5524,8 @@ async function runDaemonCommand(opts = {}) {
4861
5524
  }
4862
5525
 
4863
5526
  // src/core/registry.ts
4864
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync15 } from "fs";
4865
- import { join as join18 } from "path";
5527
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync16, writeFileSync as writeFileSync18 } from "fs";
5528
+ import { join as join21 } from "path";
4866
5529
  var FALLBACK = {
4867
5530
  version: "builtin",
4868
5531
  tools: {
@@ -4940,11 +5603,11 @@ var FALLBACK = {
4940
5603
  }
4941
5604
  };
4942
5605
  function registryCachePath() {
4943
- return join18(deskbuddyHome(), "tool-registry.json");
5606
+ return join21(deskbuddyHome(), "tool-registry.json");
4944
5607
  }
4945
5608
  function loadRegistry() {
4946
5609
  try {
4947
- return JSON.parse(readFileSync14(registryCachePath(), "utf8"));
5610
+ return JSON.parse(readFileSync16(registryCachePath(), "utf8"));
4948
5611
  } catch {
4949
5612
  return FALLBACK;
4950
5613
  }
@@ -4952,15 +5615,15 @@ function loadRegistry() {
4952
5615
  async function refreshRegistry() {
4953
5616
  try {
4954
5617
  const registry = await api.toolRegistry();
4955
- mkdirSync15(deskbuddyHome(), { recursive: true });
4956
- writeFileSync15(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
5618
+ mkdirSync18(deskbuddyHome(), { recursive: true });
5619
+ writeFileSync18(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
4957
5620
  } catch {
4958
5621
  }
4959
5622
  }
4960
5623
 
4961
5624
  // 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";
5625
+ import { chmodSync as chmodSync6, existsSync as existsSync11, mkdirSync as mkdirSync19, readFileSync as readFileSync17, renameSync as renameSync14, rmSync as rmSync13, statSync as statSync6, writeFileSync as writeFileSync19 } from "fs";
5626
+ import { dirname as dirname6, join as join22 } from "path";
4964
5627
  var CC_EVENTS = [
4965
5628
  "UserPromptSubmit",
4966
5629
  "PreToolUse",
@@ -4971,7 +5634,7 @@ var CC_EVENTS = [
4971
5634
  ];
4972
5635
  function readSettings(path) {
4973
5636
  if (!existsSync11(path)) return {};
4974
- const raw = readFileSync15(path, "utf8");
5637
+ const raw = readFileSync17(path, "utf8");
4975
5638
  if (raw.trim() === "") return {};
4976
5639
  try {
4977
5640
  return JSON.parse(raw);
@@ -4980,17 +5643,17 @@ function readSettings(path) {
4980
5643
  }
4981
5644
  }
4982
5645
  function writeSettings(path, settings) {
4983
- mkdirSync16(dirname6(path), { recursive: true });
5646
+ mkdirSync19(dirname6(path), { recursive: true });
4984
5647
  let mode;
4985
5648
  try {
4986
- mode = statSync5(path).mode & 511;
5649
+ mode = statSync6(path).mode & 511;
4987
5650
  } catch {
4988
5651
  mode = void 0;
4989
5652
  }
4990
5653
  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);
5654
+ writeFileSync19(tmp, JSON.stringify(settings, null, 2), "utf8");
5655
+ if (mode !== void 0) chmodSync6(tmp, mode);
5656
+ renameSync14(tmp, path);
4994
5657
  }
4995
5658
  function mentions(command, script) {
4996
5659
  return !!command && (command.includes(script) || command.includes(fwdSlash(script)));
@@ -5093,8 +5756,8 @@ function installCcRewake(settingsPath, wakePath, platform = process.platform) {
5093
5756
  function uninstallCcHook(settingsPath, hookPath) {
5094
5757
  const settings = readSettings(settingsPath);
5095
5758
  if (!settings.hooks) return;
5096
- const gatePath = join19(dirname6(hookPath), "gate.cjs");
5097
- const wakePath = join19(dirname6(hookPath), "wake.cjs");
5759
+ const gatePath = join22(dirname6(hookPath), "gate.cjs");
5760
+ const wakePath = join22(dirname6(hookPath), "wake.cjs");
5098
5761
  for (const event of Object.keys(settings.hooks)) {
5099
5762
  const groups = settings.hooks[event] ?? [];
5100
5763
  const kept = [];
@@ -5108,14 +5771,14 @@ function uninstallCcHook(settingsPath, hookPath) {
5108
5771
  }
5109
5772
  writeSettings(settingsPath, settings);
5110
5773
  try {
5111
- rmSync10(join19(dirname6(hookPath), "wake", "tokens"), { recursive: true, force: true });
5774
+ rmSync13(join22(dirname6(hookPath), "wake", "tokens"), { recursive: true, force: true });
5112
5775
  } catch {
5113
5776
  }
5114
5777
  }
5115
5778
 
5116
5779
  // 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";
5780
+ import { chmodSync as chmodSync7, existsSync as existsSync12, mkdirSync as mkdirSync20, readFileSync as readFileSync18, renameSync as renameSync15, statSync as statSync7, writeFileSync as writeFileSync20 } from "fs";
5781
+ import { dirname as dirname7, join as join23 } from "path";
5119
5782
  var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
5120
5783
  var CODEX_MARKER_END = "# <<< deskbuddy <<<";
5121
5784
  var CODEX_EVENTS = [
@@ -5128,7 +5791,7 @@ var CODEX_EVENTS = [
5128
5791
  ];
5129
5792
  var CODEX_TRUST_NOTICE = "\u26A0\uFE0F \u4E0B\u6B21\u542F\u52A8 Codex \u65F6\u4F1A\u8981\u6C42\u4F60\u786E\u8BA4\u4FE1\u4EFB\u8FD9\u4E2A hook\uFF0C\u8BF7\u9009\u62E9\u540C\u610F\u2014\u2014\u4E0D\u786E\u8BA4\u7684\u8BDD Codex \u4E0D\u4F1A\u6267\u884C\u5B83\u3002\n Codex \u7ECF `--remote` \u8FDE\u5E38\u9A7B app-server \u4F7F\u7528\u65F6\uFF0C\u7A97\u53E3\u91CC\u7684\u6388\u6743\u6846\u4E5F\u80FD\u5728\u8BBE\u5907\u4E0A\u786E\u8BA4\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09";
5130
5793
  function codexConfigPath() {
5131
- return join20(codexHome(), "config.toml");
5794
+ return join23(codexHome(), "config.toml");
5132
5795
  }
5133
5796
  function renderBlock(hookPath, events, tool, platform) {
5134
5797
  const command = JSON.stringify(`node ${platform === "win32" ? winPsPath(hookPath) : hookPath} ${tool}`);
@@ -5147,17 +5810,17 @@ function renderBlock(hookPath, events, tool, platform) {
5147
5810
  return lines2.join("\n");
5148
5811
  }
5149
5812
  function writeAtomic(path, content) {
5150
- mkdirSync17(dirname7(path), { recursive: true });
5813
+ mkdirSync20(dirname7(path), { recursive: true });
5151
5814
  let mode;
5152
5815
  try {
5153
- mode = statSync6(path).mode & 511;
5816
+ mode = statSync7(path).mode & 511;
5154
5817
  } catch {
5155
5818
  mode = void 0;
5156
5819
  }
5157
5820
  const tmp = path + ".deskbuddy.tmp";
5158
- writeFileSync17(tmp, content, "utf8");
5159
- if (mode !== void 0) chmodSync6(tmp, mode);
5160
- renameSync13(tmp, path);
5821
+ writeFileSync20(tmp, content, "utf8");
5822
+ if (mode !== void 0) chmodSync7(tmp, mode);
5823
+ renameSync15(tmp, path);
5161
5824
  }
5162
5825
  function assertSafeToAppend(raw, path) {
5163
5826
  for (const quote of ['"""', "'''"]) {
@@ -5167,7 +5830,7 @@ function assertSafeToAppend(raw, path) {
5167
5830
  }
5168
5831
  }
5169
5832
  function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "codex", platform = process.platform) {
5170
- const raw = existsSync12(configPath2) ? readFileSync16(configPath2, "utf8") : "";
5833
+ const raw = existsSync12(configPath2) ? readFileSync18(configPath2, "utf8") : "";
5171
5834
  if (raw.includes(CODEX_MARKER_BEGIN)) return;
5172
5835
  assertSafeToAppend(raw, configPath2);
5173
5836
  const block = renderBlock(hookPath, events, tool, platform);
@@ -5176,7 +5839,7 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
5176
5839
  }
5177
5840
  function uninstallCodexHook(configPath2) {
5178
5841
  if (!existsSync12(configPath2)) return;
5179
- const raw = readFileSync16(configPath2, "utf8");
5842
+ const raw = readFileSync18(configPath2, "utf8");
5180
5843
  const lines2 = raw.split("\n");
5181
5844
  const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
5182
5845
  if (begin === -1) return;
@@ -5190,11 +5853,11 @@ function uninstallCodexHook(configPath2) {
5190
5853
  }
5191
5854
 
5192
5855
  // src/commands/tools.ts
5193
- import { existsSync as existsSync13, readFileSync as readFileSync17 } from "fs";
5856
+ import { existsSync as existsSync13, readFileSync as readFileSync19 } from "fs";
5194
5857
  function fileContains(path, needle) {
5195
5858
  try {
5196
5859
  if (!existsSync13(path)) return false;
5197
- return readFileSync17(path, "utf8").includes(needle);
5860
+ return readFileSync19(path, "utf8").includes(needle);
5198
5861
  } catch {
5199
5862
  return false;
5200
5863
  }
@@ -5268,13 +5931,13 @@ function subscribedEvents(registry, tool, fallback) {
5268
5931
  }
5269
5932
 
5270
5933
  // src/commands/enable.ts
5271
- import { dirname as dirname9, join as join23 } from "path";
5934
+ import { dirname as dirname9, join as join26 } from "path";
5272
5935
 
5273
5936
  // 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";
5937
+ import { execFileSync as execFileSync9 } from "child_process";
5938
+ import { chmodSync as chmodSync8, existsSync as existsSync14, mkdirSync as mkdirSync21, readdirSync as readdirSync5, readFileSync as readFileSync20, rmdirSync, rmSync as rmSync14, writeFileSync as writeFileSync21 } from "fs";
5276
5939
  import { homedir as homedir5 } from "os";
5277
- import { delimiter as delimiter2, join as join21, resolve as resolve2 } from "path";
5940
+ import { delimiter as delimiter2, join as join24, resolve as resolve2 } from "path";
5278
5941
  var SHIM_FALLBACK_EXIT = 213;
5279
5942
  var WIN_SHIM_ENABLED = false;
5280
5943
  var SHIM_COMMANDS = { claude_code: "claude", codex: "codex" };
@@ -5284,10 +5947,10 @@ var RC_END = "# <<< deskbuddy <<<";
5284
5947
  var RC_FILES = [".zshrc", ".bashrc"];
5285
5948
  var WIN_PENDING = "Windows \u7A97\u53E3\u63A5\u7BA1\u5F85\u9A8C\u8BC1\uFF0C\u6682\u672A\u5F00\u542F";
5286
5949
  function shimDir() {
5287
- return join21(deskbuddyHome(), "bin");
5950
+ return join24(deskbuddyHome(), "bin");
5288
5951
  }
5289
5952
  function shimFile(binDir, command, platform = process.platform) {
5290
- return join21(binDir, platform === "win32" ? `${command}.cmd` : command);
5953
+ return join24(binDir, platform === "win32" ? `${command}.cmd` : command);
5291
5954
  }
5292
5955
  function shQuote3(s) {
5293
5956
  return `'${s.replace(/'/g, `'\\''`)}'`;
@@ -5416,10 +6079,10 @@ function removeRcBlock(content) {
5416
6079
  return content.slice(0, start) + content.slice(end);
5417
6080
  }
5418
6081
  function readRc(p) {
5419
- return readFileSync18(p).toString("latin1");
6082
+ return readFileSync20(p).toString("latin1");
5420
6083
  }
5421
6084
  function writeRc(p, latin1) {
5422
- writeFileSync18(p, Buffer.from(latin1, "latin1"));
6085
+ writeFileSync21(p, Buffer.from(latin1, "latin1"));
5423
6086
  }
5424
6087
  function toLatin1(utf8) {
5425
6088
  return Buffer.from(utf8, "utf8").toString("latin1");
@@ -5441,7 +6104,7 @@ function removePathEntry(value, dir) {
5441
6104
  var PS_READ = "$k=[Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment');$v='';if($k){$v=$k.GetValue('Path','',[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)};[Console]::Out.Write([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([string]$v)))";
5442
6105
  var PS_WRITE = "$k=[Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment');$kind=[Microsoft.Win32.RegistryValueKind]::ExpandString;try{$kind=$k.GetValueKind('Path')}catch{};if([string]::IsNullOrEmpty($env:DESKBUDDY_NEW_PATH)){$k.DeleteValue('Path',$false)}else{$k.SetValue('Path',$env:DESKBUDDY_NEW_PATH,$kind)};[Environment]::SetEnvironmentVariable('DESKBUDDY_PATH_REFRESH','1','User');[Environment]::SetEnvironmentVariable('DESKBUDDY_PATH_REFRESH',$null,'User')";
5443
6106
  function psWinPath() {
5444
- const ps = (script, env) => execFileSync8("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
6107
+ const ps = (script, env) => execFileSync9("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
5445
6108
  encoding: "utf8",
5446
6109
  stdio: ["ignore", "pipe", "ignore"],
5447
6110
  windowsHide: true,
@@ -5488,12 +6151,12 @@ function installShim(tool, e) {
5488
6151
  if (!node || !cli || !bin) {
5489
6152
  return { ok: false, message: "\u7A97\u53E3\u63A5\u7BA1\u6CA1\u88C5\uFF1A\u5B89\u88C5\u8DEF\u5F84\u91CC\u6709\u975E ASCII \u5B57\u7B26\uFF0CWindows \u7684 .cmd \u8BFB\u4E0D\u4E86\uFF08\u4E0D\u5F71\u54CD\u5176\u5B83\u529F\u80FD\uFF09" };
5490
6153
  }
5491
- mkdirSync18(e.binDir, { recursive: true });
5492
- writeFileSync18(file, cmdScript(tool, command, node, cli, bin), "utf8");
6154
+ mkdirSync21(e.binDir, { recursive: true });
6155
+ writeFileSync21(file, cmdScript(tool, command, node, cli, bin), "utf8");
5493
6156
  } 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);
6157
+ mkdirSync21(e.binDir, { recursive: true, mode: 448 });
6158
+ writeFileSync21(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
6159
+ chmodSync8(file, 493);
5497
6160
  }
5498
6161
  const touched = [];
5499
6162
  if (e.platform === "win32") {
@@ -5507,7 +6170,7 @@ function installShim(tool, e) {
5507
6170
  } else if (e.rcHome) {
5508
6171
  const block = toLatin1(rcBlock(e.binDir, e.rcHome));
5509
6172
  for (const name of RC_FILES) {
5510
- const rc = join21(e.rcHome, name);
6173
+ const rc = join24(e.rcHome, name);
5511
6174
  if (!existsSync14(rc)) continue;
5512
6175
  const next = addRcBlock(readRc(rc), block);
5513
6176
  if (next !== null) writeRc(rc, next);
@@ -5528,13 +6191,13 @@ function installShim(tool, e) {
5528
6191
  function ourShims(binDir) {
5529
6192
  let names;
5530
6193
  try {
5531
- names = readdirSync4(binDir);
6194
+ names = readdirSync5(binDir);
5532
6195
  } catch {
5533
6196
  return [];
5534
6197
  }
5535
6198
  return names.filter((n) => {
5536
6199
  try {
5537
- return readFileSync18(join21(binDir, n), "utf8").includes(SHIM_MARKER);
6200
+ return readFileSync20(join24(binDir, n), "utf8").includes(SHIM_MARKER);
5538
6201
  } catch {
5539
6202
  return false;
5540
6203
  }
@@ -5546,8 +6209,8 @@ function uninstallShim(tool, e) {
5546
6209
  let removed = false;
5547
6210
  const file = shimFile(e.binDir, command, e.platform);
5548
6211
  try {
5549
- if (existsSync14(file) && readFileSync18(file, "utf8").includes(SHIM_MARKER)) {
5550
- rmSync11(file, { force: true });
6212
+ if (existsSync14(file) && readFileSync20(file, "utf8").includes(SHIM_MARKER)) {
6213
+ rmSync14(file, { force: true });
5551
6214
  removed = true;
5552
6215
  }
5553
6216
  } catch {
@@ -5565,7 +6228,7 @@ function uninstallShim(tool, e) {
5565
6228
  }
5566
6229
  } else if (e.rcHome) {
5567
6230
  for (const name of RC_FILES) {
5568
- const rc = join21(e.rcHome, name);
6231
+ const rc = join24(e.rcHome, name);
5569
6232
  if (!existsSync14(rc)) continue;
5570
6233
  const next = removeRcBlock(readRc(rc));
5571
6234
  if (next !== null) {
@@ -5575,7 +6238,7 @@ function uninstallShim(tool, e) {
5575
6238
  }
5576
6239
  }
5577
6240
  try {
5578
- if (existsSync14(e.binDir) && readdirSync4(e.binDir).length === 0) rmdirSync(e.binDir);
6241
+ if (existsSync14(e.binDir) && readdirSync5(e.binDir).length === 0) rmdirSync(e.binDir);
5579
6242
  } catch {
5580
6243
  }
5581
6244
  if (!removed && touched.length === 0) return { ok: true, message: "" };
@@ -5587,7 +6250,7 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
5587
6250
  const file = shimFile(binDir, command, platform);
5588
6251
  let installed = false;
5589
6252
  try {
5590
- installed = readFileSync18(file, "utf8").includes(SHIM_MARKER);
6253
+ installed = readFileSync20(file, "utf8").includes(SHIM_MARKER);
5591
6254
  } catch {
5592
6255
  installed = false;
5593
6256
  }
@@ -5598,16 +6261,16 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
5598
6261
  }
5599
6262
 
5600
6263
  // src/wrap/pty.ts
5601
- import { chmodSync as chmodSync8, existsSync as existsSync15, statSync as statSync7 } from "fs";
6264
+ import { chmodSync as chmodSync9, existsSync as existsSync15, statSync as statSync8 } from "fs";
5602
6265
  import { createRequire as createRequire2 } from "module";
5603
- import { dirname as dirname8, join as join22 } from "path";
6266
+ import { dirname as dirname8, join as join25 } from "path";
5604
6267
  var req = createRequire2(import.meta.url);
5605
6268
  function fixSpawnHelper() {
5606
6269
  if (process.platform !== "darwin") return;
5607
6270
  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);
6271
+ const dir = join25(dirname8(req.resolve("node-pty/package.json")), "prebuilds", `darwin-${process.arch}`);
6272
+ const helper = join25(dir, "spawn-helper");
6273
+ if (existsSync15(helper) && (statSync8(helper).mode & 73) === 0) chmodSync9(helper, 493);
5611
6274
  } catch {
5612
6275
  }
5613
6276
  }
@@ -5702,14 +6365,14 @@ function installTools(tools, out, shim = defaultShimEnvForInstall()) {
5702
6365
  installed.push(tool);
5703
6366
  out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
5704
6367
  try {
5705
- if (adapter.installWindowGate?.(configPath2, join23(dirname9(hookPath), "gate.cjs"), tool)) {
6368
+ if (adapter.installWindowGate?.(configPath2, join26(dirname9(hookPath), "gate.cjs"), tool)) {
5706
6369
  out(` \u7A97\u53E3\u91CC\u5F39\u6388\u6743\u6846\u65F6\uFF0C\u8BBE\u5907\u4E0A\u4E5F\u80FD\u786E\u8BA4\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
5707
6370
  }
5708
6371
  } catch (e) {
5709
6372
  out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
5710
6373
  }
5711
6374
  try {
5712
- if (adapter.installRewake?.(configPath2, join23(dirname9(hookPath), "wake.cjs"), tool)) {
6375
+ if (adapter.installRewake?.(configPath2, join26(dirname9(hookPath), "wake.cjs"), tool)) {
5713
6376
  out(` \u7A97\u53E3\u5F00\u7740\u65F6\uFF0C\u8BBE\u5907\u6307\u4EE4\u76F4\u63A5\u5728\u8FD9\u4E2A\u7A97\u53E3\u91CC\u6267\u884C\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
5714
6377
  }
5715
6378
  } catch (e) {
@@ -5809,8 +6472,8 @@ async function runDisable(tools, opts = {}) {
5809
6472
  }
5810
6473
 
5811
6474
  // src/commands/install.ts
5812
- import { mkdirSync as mkdirSync19, rmSync as rmSync12, writeFileSync as writeFileSync19 } from "fs";
5813
- import { join as join24 } from "path";
6475
+ import { mkdirSync as mkdirSync22, rmSync as rmSync15, writeFileSync as writeFileSync22 } from "fs";
6476
+ import { join as join27 } from "path";
5814
6477
 
5815
6478
  // src/core/detect.ts
5816
6479
  import { existsSync as existsSync16 } from "fs";
@@ -5855,7 +6518,7 @@ function detectTools(d = defaultDetectDeps()) {
5855
6518
  import qrcode from "qrcode-terminal";
5856
6519
 
5857
6520
  // src/commands/remoteCwd.ts
5858
- import { existsSync as existsSync17, statSync as statSync8 } from "fs";
6521
+ import { existsSync as existsSync17, statSync as statSync9 } from "fs";
5859
6522
  import { createInterface } from "readline";
5860
6523
  import { homedir as homedir6 } from "os";
5861
6524
  import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
@@ -5869,7 +6532,7 @@ function normalizePath(raw, home = homedir6()) {
5869
6532
  function checkPath(abs) {
5870
6533
  if (!existsSync17(abs)) return "missing";
5871
6534
  try {
5872
- return statSync8(abs).isDirectory() ? "ok" : "not_a_dir";
6535
+ return statSync9(abs).isDirectory() ? "ok" : "not_a_dir";
5873
6536
  } catch {
5874
6537
  return "missing";
5875
6538
  }
@@ -6071,11 +6734,11 @@ function inSandbox(env) {
6071
6734
  }
6072
6735
  function precheckWritable(dirs) {
6073
6736
  for (const dir of dirs) {
6074
- const probe = join24(dir, `.deskbuddy-probe-${process.pid}`);
6737
+ const probe = join27(dir, `.deskbuddy-probe-${process.pid}`);
6075
6738
  try {
6076
- mkdirSync19(dir, { recursive: true });
6077
- writeFileSync19(probe, "");
6078
- rmSync12(probe, { force: true });
6739
+ mkdirSync22(dir, { recursive: true });
6740
+ writeFileSync22(probe, "");
6741
+ rmSync15(probe, { force: true });
6079
6742
  } catch (e) {
6080
6743
  return { ok: false, dir, error: e.message };
6081
6744
  }
@@ -6204,7 +6867,7 @@ async function runInstall(opts, deps = defaultInstallDeps()) {
6204
6867
 
6205
6868
  // src/commands/logout.ts
6206
6869
  import { createInterface as createInterface2 } from "readline";
6207
- import { existsSync as existsSync18, rmSync as rmSync13 } from "fs";
6870
+ import { existsSync as existsSync18, rmSync as rmSync16 } from "fs";
6208
6871
  function stdout4(line) {
6209
6872
  process.stdout.write(line + "\n");
6210
6873
  }
@@ -6236,7 +6899,7 @@ async function runLogout(opts = {}) {
6236
6899
  }
6237
6900
  await runDisable([], { out, brief: true, keepAutostart: opts.keepAutostart });
6238
6901
  if (existsSync18(home)) {
6239
- rmSync13(home, { recursive: true, force: true });
6902
+ rmSync16(home, { recursive: true, force: true });
6240
6903
  }
6241
6904
  out("");
6242
6905
  out("\u2705 \u5DF2\u6E05\u9664\u672C\u673A\u7684 DeskBuddy \u6570\u636E\u3002");
@@ -6396,45 +7059,15 @@ async function runUpdate(opts = {}) {
6396
7059
  }
6397
7060
 
6398
7061
  // 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";
7062
+ import { randomBytes as randomBytes8 } from "crypto";
7063
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync24, statSync as statSync11, truncateSync as truncateSync3 } from "fs";
7064
+ import { join as join29 } from "path";
6402
7065
  import { StringDecoder } from "string_decoder";
6403
7066
 
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
7067
  // src/wrap/injector.ts
7068
+ var SEEN_MAX = 200;
6437
7069
  var QUIET_MS = 1500;
7070
+ var STARTUP_QUIET_MS = 3e3;
6438
7071
  var KEY_QUIET_MS = 3e3;
6439
7072
  var PASTE_SETTLE_MS = 400;
6440
7073
  var SUBMIT_WAIT_MS = 1e4;
@@ -6446,6 +7079,8 @@ var Injector = class {
6446
7079
  #lastKey = 0;
6447
7080
  #busy = false;
6448
7081
  #closed = false;
7082
+ /** instructionId → 那一条(去重:daemon 等 ack 超时重送时,不能写进窗口两遍) */
7083
+ #seen = /* @__PURE__ */ new Map();
6449
7084
  constructor(deps) {
6450
7085
  this.#d = deps;
6451
7086
  this.#lastOut = deps.now();
@@ -6465,7 +7100,27 @@ var Injector = class {
6465
7100
  if (i >= 0) this.#queue.splice(i, 1);
6466
7101
  if (job.done) return;
6467
7102
  job.done = true;
6468
- job.reply({ instructionId: job.instructionId, ...msg });
7103
+ job.finalMsg = { instructionId: job.instructionId, ...msg };
7104
+ job.reply(job.finalMsg);
7105
+ }
7106
+ /** 记下这条,超过上限丢最早的 */
7107
+ #remember(job) {
7108
+ this.#seen.set(job.instructionId, job);
7109
+ while (this.#seen.size > SEEN_MAX) this.#seen.delete(this.#seen.keys().next().value);
7110
+ }
7111
+ /**
7112
+ * 同一 instructionId 又送来了(daemon 没等到 ack 重送、而第一次其实已收下):不再排队,
7113
+ * 把后续进展改回给新连接;已有终态的原样再回一次
7114
+ */
7115
+ #rebind(old, job) {
7116
+ job.reply({ instructionId: job.instructionId, status: "ack" });
7117
+ if (old.done) {
7118
+ if (old.finalMsg) job.reply(old.finalMsg);
7119
+ return;
7120
+ }
7121
+ old.reply = job.reply;
7122
+ old.alive = job.alive;
7123
+ if (old.queuedSent) job.reply({ instructionId: job.instructionId, status: "queued", mode: "window", reason: "" });
6469
7124
  }
6470
7125
  /**
6471
7126
  * 这个会话还是不是本窗口当前的会话。
@@ -6476,19 +7131,35 @@ var Injector = class {
6476
7131
  #owns(sessionId) {
6477
7132
  const last = this.#d.lastEvent();
6478
7133
  if (!last) return true;
7134
+ if (!sessionId) return last.ev !== "SessionEnd";
6479
7135
  return last.sid === sessionId && last.ev !== "SessionEnd";
6480
7136
  }
6481
7137
  /** 通道收到一条指令 */
6482
7138
  submit(job) {
7139
+ const seen = this.#seen.get(job.instructionId);
7140
+ if (seen) {
7141
+ this.#rebind(seen, job);
7142
+ return;
7143
+ }
6483
7144
  if (this.#closed) {
6484
7145
  job.reply({ instructionId: job.instructionId, status: "not_here" });
6485
7146
  return;
6486
7147
  }
7148
+ if (tooLongToInject(job.text)) {
7149
+ this.#remember(job);
7150
+ job.done = true;
7151
+ job.finalMsg = { instructionId: job.instructionId, status: "rejected", mode: "window", reason: TEXT_TOO_LONG };
7152
+ job.reply(job.finalMsg);
7153
+ return;
7154
+ }
6487
7155
  if (!this.#owns(job.sessionId)) {
7156
+ this.#remember(job);
6488
7157
  job.done = true;
6489
- job.reply({ instructionId: job.instructionId, status: "rejected", mode: "window", reason: "session_gone" });
7158
+ job.finalMsg = { instructionId: job.instructionId, status: "rejected", mode: "window", reason: "session_gone" };
7159
+ job.reply(job.finalMsg);
6490
7160
  return;
6491
7161
  }
7162
+ this.#remember(job);
6492
7163
  job.phase = "queued";
6493
7164
  job.reply({ instructionId: job.instructionId, status: "ack" });
6494
7165
  this.#queue.push(job);
@@ -6509,10 +7180,11 @@ var Injector = class {
6509
7180
  if (v.input === null) why.push("no-input-box");
6510
7181
  else if (v.input !== "") why.push("user-typing");
6511
7182
  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);
7183
+ if (!last) {
7184
+ if (!this.#d.startupIdle) why.push("hook:none");
7185
+ } else if (!IDLE_EVENTS.has(last.ev)) why.push("hook:" + last.ev);
6514
7186
  const now = this.#d.now();
6515
- if (now - this.#lastOut < QUIET_MS) why.push("screen-busy");
7187
+ if (now - this.#lastOut < (last ? QUIET_MS : STARTUP_QUIET_MS)) why.push("screen-busy");
6516
7188
  if (now - this.#lastKey < KEY_QUIET_MS) why.push("recent-key");
6517
7189
  return why;
6518
7190
  }
@@ -6523,6 +7195,7 @@ var Injector = class {
6523
7195
  if (job.alive && !job.alive()) {
6524
7196
  this.#queue.shift();
6525
7197
  job.done = true;
7198
+ this.#seen.delete(job.instructionId);
6526
7199
  this.#d.log?.("daemon \u8FDE\u63A5\u5DF2\u65AD\uFF0C\u4E22\u5F03\u672A\u5199\u5165\u7684\u4E00\u6761");
6527
7200
  return;
6528
7201
  }
@@ -6609,8 +7282,8 @@ var Injector = class {
6609
7282
  };
6610
7283
 
6611
7284
  // src/wrap/passthrough.ts
6612
- import { spawn as spawn2 } from "child_process";
6613
- import { realpathSync as realpathSync2 } from "fs";
7285
+ import { spawn as spawn3 } from "child_process";
7286
+ import { realpathSync as realpathSync3 } from "fs";
6614
7287
  import { constants as constants2 } from "os";
6615
7288
  import { dirname as dirname11, resolve as resolve4 } from "path";
6616
7289
  var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
@@ -6668,7 +7341,7 @@ function norm(p, win) {
6668
7341
  }
6669
7342
  function real(p) {
6670
7343
  try {
6671
- return realpathSync2(p);
7344
+ return realpathSync3(p);
6672
7345
  } catch {
6673
7346
  return p;
6674
7347
  }
@@ -6683,6 +7356,9 @@ function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
6683
7356
  const loc = locate(tool, dd);
6684
7357
  return loc && !inSkip(loc.prefixArgs[0] && /\.(?:c|m)?js$/i.test(loc.prefixArgs[0]) ? loc.prefixArgs[0] : loc.file) ? loc : null;
6685
7358
  }
7359
+ if (win && tool === "claude_code") {
7360
+ for (const p of winClaudeExeCandidates(dd)) if (dd.exists(p) && !inSkip(p)) return { file: p, prefixArgs: [] };
7361
+ }
6686
7362
  for (const f of whichAllIn(name, dd)) {
6687
7363
  if (inSkip(f)) continue;
6688
7364
  const loc = win ? resolveWinLaunch(f, dd) : { file: f, prefixArgs: [] };
@@ -6702,7 +7378,7 @@ function runDirect(real2, args, env = process.env) {
6702
7378
  return new Promise((done) => {
6703
7379
  let child;
6704
7380
  try {
6705
- child = spawn2(real2.file, [...real2.prefixArgs, ...args], {
7381
+ child = spawn3(real2.file, [...real2.prefixArgs, ...args], {
6706
7382
  stdio: "inherit",
6707
7383
  // 🔴 再经过垫片也直接放行,不会绕回来
6708
7384
  env: withoutShimFlag({ ...env, DESKBUDDY_WRAPPED: "1" }),
@@ -6752,21 +7428,21 @@ function runDirect(real2, args, env = process.env) {
6752
7428
  }
6753
7429
 
6754
7430
  // 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";
7431
+ import { spawn as spawn4 } from "child_process";
7432
+ import { closeSync as closeSync3, mkdirSync as mkdirSync23, openSync as openSync3, readdirSync as readdirSync6, readFileSync as readFileSync21, rmSync as rmSync17, statSync as statSync10, truncateSync as truncateSync2, writeFileSync as writeFileSync23, renameSync as renameSync16 } from "fs";
6757
7433
  import { request } from "http";
6758
7434
  import { createServer as createServer4 } from "net";
6759
- import { join as join25 } from "path";
7435
+ import { join as join28 } from "path";
6760
7436
 
6761
7437
  // src/core/codexServer.ts
6762
- import { execFileSync as execFileSync9 } from "child_process";
7438
+ import { execFileSync as execFileSync10 } from "child_process";
6763
7439
  function isAppServerArgs(args, url) {
6764
7440
  return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
6765
7441
  }
6766
7442
  function processArgs(pid) {
6767
7443
  if (process.platform === "win32") return null;
6768
7444
  try {
6769
- const out = execFileSync9("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
7445
+ const out = execFileSync10("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
6770
7446
  encoding: "utf8",
6771
7447
  stdio: ["ignore", "pipe", "ignore"]
6772
7448
  });
@@ -6924,10 +7600,10 @@ async function runCodexWindow(real2, args, d) {
6924
7600
  }
6925
7601
  var MAX_LOG_BYTES2 = 1024 * 1024;
6926
7602
  function appServerLogPath() {
6927
- return join25(deskbuddyHome(), "codex-app-server.log");
7603
+ return join28(deskbuddyHome(), "codex-app-server.log");
6928
7604
  }
6929
7605
  function windowsDir2() {
6930
- return join25(deskbuddyHome(), "codex-windows");
7606
+ return join28(deskbuddyHome(), "codex-windows");
6931
7607
  }
6932
7608
  function pidAlive(pid) {
6933
7609
  try {
@@ -6940,7 +7616,7 @@ function pidAlive(pid) {
6940
7616
  function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
6941
7617
  let pid = 0;
6942
7618
  try {
6943
- pid = Number(readFileSync19(pidFile, "utf8").trim());
7619
+ pid = Number(readFileSync21(pidFile, "utf8").trim());
6944
7620
  } catch {
6945
7621
  return false;
6946
7622
  }
@@ -6974,16 +7650,16 @@ function freePort() {
6974
7650
  }
6975
7651
  function startServer(real2, url, env, cwd) {
6976
7652
  const logPath2 = appServerLogPath();
6977
- mkdirSync20(deskbuddyHome(), { recursive: true });
7653
+ mkdirSync23(deskbuddyHome(), { recursive: true });
6978
7654
  try {
6979
- if (statSync9(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
7655
+ if (statSync10(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
6980
7656
  } catch {
6981
7657
  }
6982
- const fd = openSync2(logPath2, "a");
7658
+ const fd = openSync3(logPath2, "a");
6983
7659
  try {
6984
7660
  const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
6985
7661
  delete childEnv2.DESKBUDDY_SHIM;
6986
- const child = spawn3(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
7662
+ const child = spawn4(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
6987
7663
  detached: true,
6988
7664
  stdio: ["ignore", fd, fd],
6989
7665
  cwd,
@@ -6994,7 +7670,7 @@ function startServer(real2, url, env, cwd) {
6994
7670
  child.unref();
6995
7671
  return child.pid ?? null;
6996
7672
  } finally {
6997
- closeSync2(fd);
7673
+ closeSync3(fd);
6998
7674
  }
6999
7675
  }
7000
7676
  function killServer(pid) {
@@ -7008,41 +7684,41 @@ function killOwnServer(pid, url) {
7008
7684
  if (a && isAppServerArgs(a, url)) killServer(pid);
7009
7685
  }
7010
7686
  function recordPath(rec) {
7011
- return join25(windowsDir2(), `${rec.server}.json`);
7687
+ return join28(windowsDir2(), `${rec.server}.json`);
7012
7688
  }
7013
7689
  function writeRecord(rec) {
7014
7690
  try {
7015
- mkdirSync20(windowsDir2(), { recursive: true, mode: 448 });
7691
+ mkdirSync23(windowsDir2(), { recursive: true, mode: 448 });
7016
7692
  const p = recordPath(rec);
7017
- writeFileSync20(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
7018
- renameSync14(p + ".tmp", p);
7693
+ writeFileSync23(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
7694
+ renameSync16(p + ".tmp", p);
7019
7695
  } catch {
7020
7696
  }
7021
7697
  }
7022
7698
  function removeRecord(rec) {
7023
7699
  try {
7024
- rmSync14(recordPath(rec), { force: true });
7700
+ rmSync17(recordPath(rec), { force: true });
7025
7701
  } catch {
7026
7702
  }
7027
7703
  }
7028
7704
  function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs, kill = killServer) {
7029
7705
  let names = [];
7030
7706
  try {
7031
- names = readdirSync5(dir).filter((n) => n.endsWith(".json"));
7707
+ names = readdirSync6(dir).filter((n) => n.endsWith(".json"));
7032
7708
  } catch {
7033
7709
  return;
7034
7710
  }
7035
7711
  for (const n of names) {
7036
- const p = join25(dir, n);
7712
+ const p = join28(dir, n);
7037
7713
  let rec;
7038
7714
  try {
7039
- rec = JSON.parse(readFileSync19(p, "utf8"));
7715
+ rec = JSON.parse(readFileSync21(p, "utf8"));
7040
7716
  } catch {
7041
- rmSync14(p, { force: true });
7717
+ rmSync17(p, { force: true });
7042
7718
  continue;
7043
7719
  }
7044
7720
  if (typeof rec.supervisor !== "number" || typeof rec.server !== "number" || typeof rec.url !== "string") {
7045
- rmSync14(p, { force: true });
7721
+ rmSync17(p, { force: true });
7046
7722
  continue;
7047
7723
  }
7048
7724
  if (alive(rec.supervisor)) continue;
@@ -7050,13 +7726,13 @@ function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs,
7050
7726
  const a = args(rec.server);
7051
7727
  if (a && isAppServerArgs(a, rec.url)) kill(rec.server);
7052
7728
  }
7053
- rmSync14(p, { force: true });
7729
+ rmSync17(p, { force: true });
7054
7730
  }
7055
7731
  }
7056
7732
  function launchLog(line) {
7057
7733
  try {
7058
- mkdirSync20(deskbuddyHome(), { recursive: true });
7059
- writeFileSync20(join25(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
7734
+ mkdirSync23(deskbuddyHome(), { recursive: true });
7735
+ writeFileSync23(join28(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
7060
7736
  `, { flag: "a" });
7061
7737
  } catch {
7062
7738
  }
@@ -7096,10 +7772,10 @@ function childEnv(src, windowId) {
7096
7772
  }
7097
7773
  function wrapLog(line) {
7098
7774
  try {
7099
- const p = join26(deskbuddyHome(), "wrap.log");
7100
- mkdirSync21(deskbuddyHome(), { recursive: true });
7775
+ const p = join29(deskbuddyHome(), "wrap.log");
7776
+ mkdirSync24(deskbuddyHome(), { recursive: true });
7101
7777
  try {
7102
- if (statSync10(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
7778
+ if (statSync11(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
7103
7779
  } catch {
7104
7780
  }
7105
7781
  appendFileSync2(p, `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${line}
@@ -7121,6 +7797,7 @@ var defaultWrapDeps = () => ({
7121
7797
  log: wrapLog
7122
7798
  });
7123
7799
  async function runWrap(argv, deps = defaultWrapDeps()) {
7800
+ if (argv[0] === "--launch") return runLaunch(argv.slice(1), deps);
7124
7801
  const viaShim = deps.env.DESKBUDDY_SHIM === "1";
7125
7802
  const r = await runWrapInner(argv, deps);
7126
7803
  if (r === "not_found") {
@@ -7178,7 +7855,59 @@ async function runWrapInner(argv, deps) {
7178
7855
  if (r !== null) return r;
7179
7856
  return deps.runDirect(real2, args);
7180
7857
  }
7181
- async function wrapInPty(tool, real2, args, deps) {
7858
+ var defaultLaunchDeps = () => ({
7859
+ claim: claimLaunch,
7860
+ isDir: isDirSync,
7861
+ chdir: (p) => process.chdir(p)
7862
+ });
7863
+ async function runLaunch(argv, deps = defaultWrapDeps(), ld = defaultLaunchDeps()) {
7864
+ const runId = argv[0] ?? "";
7865
+ const hi = argv.indexOf("--home");
7866
+ const home = hi >= 0 ? argv[hi + 1] ?? "" : "";
7867
+ if (!RUN_ID_RE.test(runId)) {
7868
+ deps.stderr.write("deskbuddy: \u542F\u52A8\u53C2\u6570\u4E0D\u5BF9\n");
7869
+ return 2;
7870
+ }
7871
+ if (home) {
7872
+ if (!/^(?:[A-Za-z]:[\\/]|\/)/.test(home)) {
7873
+ deps.stderr.write("deskbuddy: \u542F\u52A8\u53C2\u6570\u4E0D\u5BF9\n");
7874
+ return 2;
7875
+ }
7876
+ process.env.DESKBUDDY_HOME = home;
7877
+ deps.env.DESKBUDDY_HOME = home;
7878
+ }
7879
+ const plan = ld.claim(runId);
7880
+ if (!plan) {
7881
+ deps.stderr.write("deskbuddy: \u8FD9\u4E2A\u7A97\u53E3\u8981\u505A\u7684\u4E8B\u5DF2\u7ECF\u53D6\u6D88\u6216\u6539\u5728\u540E\u53F0\u6267\u884C\u4E86\uFF0C\u53EF\u4EE5\u5173\u6389\u8FD9\u4E2A\u7A97\u53E3\n");
7882
+ return 1;
7883
+ }
7884
+ if (!ld.isDir(plan.cwd)) {
7885
+ deps.stderr.write("deskbuddy: \u5DE5\u4F5C\u76EE\u5F55\u5DF2\u7ECF\u4E0D\u5728\u4E86\n");
7886
+ return 1;
7887
+ }
7888
+ ld.chdir(plan.cwd);
7889
+ const args = plan.resume ? ["--resume", plan.resume] : [];
7890
+ let real2 = null;
7891
+ try {
7892
+ real2 = deps.findReal("claude_code");
7893
+ } catch {
7894
+ real2 = null;
7895
+ }
7896
+ if (!real2) {
7897
+ deps.stderr.write("deskbuddy: \u627E\u4E0D\u5230 claude\n");
7898
+ return 127;
7899
+ }
7900
+ const d2 = { ...deps, cwd: () => plan.cwd };
7901
+ let r = null;
7902
+ try {
7903
+ r = await wrapInPty("claude_code", real2, args, d2, { windowId: runId, launched: true });
7904
+ } catch (e) {
7905
+ deps.log(`\u5F00\u7A97\u5305\u88C5\u5931\u8D25\uFF0C\u76F4\u63A5\u8FD0\u884C\uFF1A${e.message}`);
7906
+ }
7907
+ if (r !== null) return r;
7908
+ return deps.runDirect(real2, args);
7909
+ }
7910
+ async function wrapInPty(tool, real2, args, deps, opts = {}) {
7182
7911
  const pty = deps.loadPty();
7183
7912
  const Headless = deps.loadHeadless();
7184
7913
  if (!pty || !Headless) {
@@ -7186,7 +7915,7 @@ async function wrapInPty(tool, real2, args, deps) {
7186
7915
  return null;
7187
7916
  }
7188
7917
  const { stdout: stdout7 } = deps;
7189
- const windowId = randomBytes7(16).toString("hex");
7918
+ const windowId = opts.windowId ?? randomBytes8(16).toString("hex");
7190
7919
  const cols = stdout7.columns || 80;
7191
7920
  const rows = stdout7.rows || 24;
7192
7921
  const term = new Headless({ cols, rows, allowProposedApi: true });
@@ -7203,9 +7932,9 @@ async function wrapInPty(tool, real2, args, deps) {
7203
7932
  deps.log(`pty \u8D77\u4E0D\u6765\uFF0C\u653E\u884C\u76F4\u8DD1\uFF1A${e.message}`);
7204
7933
  return null;
7205
7934
  }
7206
- return runAttached(tool, p, term, windowId, deps);
7935
+ return runAttached(tool, p, term, windowId, deps, opts.launched === true);
7207
7936
  }
7208
- function runAttached(tool, p, term0, windowId, deps) {
7937
+ function runAttached(tool, p, term0, windowId, deps, launched = false) {
7209
7938
  const { stdin, stdout: stdout7 } = deps;
7210
7939
  let term = term0;
7211
7940
  let finished = false;
@@ -7235,7 +7964,8 @@ function runAttached(tool, p, term0, windowId, deps) {
7235
7964
  held = null;
7236
7965
  for (const s of h) toPty(s);
7237
7966
  },
7238
- log: (m) => deps.log(`[${windowId.slice(0, 8)}] ${m}`)
7967
+ log: (m) => deps.log(`[${windowId.slice(0, 8)}] ${m}`),
7968
+ startupIdle: launched
7239
7969
  });
7240
7970
  p.onData((d) => {
7241
7971
  try {
@@ -7292,8 +8022,18 @@ function runAttached(tool, p, term0, windowId, deps) {
7292
8022
  } catch (e) {
7293
8023
  deps.log(`\u63A5\u7BA1\u7EC8\u7AEF\u51FA\u9519\uFF1A${e.message}`);
7294
8024
  }
8025
+ const secret = randomBytes8(32);
7295
8026
  try {
7296
- writeWindow(windowId, { pid: process.pid, childPid: p.pid, sock, tool, cwd: deps.cwd(), at: Date.now() });
8027
+ writeWindow(windowId, {
8028
+ pid: process.pid,
8029
+ childPid: p.pid,
8030
+ sock,
8031
+ tool,
8032
+ cwd: deps.cwd(),
8033
+ at: Date.now(),
8034
+ secret: secret.toString("hex"),
8035
+ ...launched ? { launched: true } : {}
8036
+ });
7297
8037
  } catch (e) {
7298
8038
  deps.log(`\u767B\u8BB0\u7A97\u53E3\u5931\u8D25\uFF1A${e.message}`);
7299
8039
  }
@@ -7301,6 +8041,7 @@ function runAttached(tool, p, term0, windowId, deps) {
7301
8041
  if (channelOk) {
7302
8042
  void startChannelServer(
7303
8043
  sock,
8044
+ secret,
7304
8045
  (req2, reply, alive) => {
7305
8046
  try {
7306
8047
  injector.submit({ ...req2, expireAt: req2.expireAt || Date.now() + DEFAULT_TTL_MS, reply, alive });