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