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