deskbuddy 0.2.0-beta.2 → 0.2.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1862 -344
- package/dist/hook.cjs +77 -4
- package/dist/wake.cjs +764 -0
- package/hook/hook.cjs +77 -4
- package/hook/wake.cjs +764 -0
- package/package.json +2 -2
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
|
-
const SIBLINGS = ["gate.cjs", "opencode.mjs"];
|
|
748
|
+
const SIBLINGS = ["gate.cjs", "opencode.mjs", "wake.cjs"];
|
|
624
749
|
const siblingMissing = () => SIBLINGS.some((name) => {
|
|
625
|
-
if (existsSync5(
|
|
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":
|
|
@@ -1295,7 +1442,7 @@ function buildSpawnPlan(cmd, cwd, gate, home = homedir4()) {
|
|
|
1295
1442
|
env: { DESKBUDDY_GATE_RUN: gate.runId, DESKBUDDY_NODE: gate.nodePath, OPENCODE_CONFIG_CONTENT: OPENCODE_DENY_ALL }
|
|
1296
1443
|
} : null;
|
|
1297
1444
|
default:
|
|
1298
|
-
return { file, args: claudePlan(cmd, gate), cwd };
|
|
1445
|
+
return { file, args: claudePlan(cmd, gate), cwd, env: { DESKBUDDY_NO_WAKE: "1" } };
|
|
1299
1446
|
}
|
|
1300
1447
|
}
|
|
1301
1448
|
function materialize(plan, tool, d = defaultLocateDeps()) {
|
|
@@ -1451,8 +1598,8 @@ function killTree(child) {
|
|
|
1451
1598
|
|
|
1452
1599
|
// src/daemon/gate/approvals.ts
|
|
1453
1600
|
import { createHash as createHash2 } from "crypto";
|
|
1454
|
-
import { mkdirSync as
|
|
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");
|
|
@@ -2437,10 +2584,14 @@ var SessionQueue = class {
|
|
|
2437
2584
|
* rejected promise,后续所有指令都会跟着被拒 —— 一次失败让这个会话**永久**
|
|
2438
2585
|
* 收不到指令,而且没有任何报错。
|
|
2439
2586
|
*/
|
|
2440
|
-
enqueue(sessionId, job) {
|
|
2587
|
+
enqueue(sessionId, job, onError) {
|
|
2441
2588
|
const key = sessionId || "";
|
|
2442
2589
|
const prev = this.#tails.get(key) ?? Promise.resolve();
|
|
2443
|
-
const next = prev.then(job).catch(() => {
|
|
2590
|
+
const next = prev.then(job).catch((e) => {
|
|
2591
|
+
try {
|
|
2592
|
+
onError?.(e);
|
|
2593
|
+
} catch {
|
|
2594
|
+
}
|
|
2444
2595
|
});
|
|
2445
2596
|
this.#tails.set(key, next);
|
|
2446
2597
|
void next.then(() => {
|
|
@@ -2465,18 +2616,18 @@ var SessionQueue = class {
|
|
|
2465
2616
|
};
|
|
2466
2617
|
|
|
2467
2618
|
// src/daemon/remote.ts
|
|
2468
|
-
import { mkdirSync as
|
|
2469
|
-
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";
|
|
2470
2621
|
function handleDir() {
|
|
2471
|
-
return
|
|
2622
|
+
return join14(deskbuddyHome(), "remote");
|
|
2472
2623
|
}
|
|
2473
2624
|
function handlePath(tool) {
|
|
2474
2625
|
const safe = (tool || "").replace(/[^0-9a-zA-Z_-]/g, "");
|
|
2475
|
-
return
|
|
2626
|
+
return join14(handleDir(), `${safe || "unknown"}.json`);
|
|
2476
2627
|
}
|
|
2477
2628
|
function readHandle(tool) {
|
|
2478
2629
|
try {
|
|
2479
|
-
const h = JSON.parse(
|
|
2630
|
+
const h = JSON.parse(readFileSync11(handlePath(tool), "utf8"));
|
|
2480
2631
|
if (typeof h?.sessionId !== "string" || !h.sessionId) return null;
|
|
2481
2632
|
if (typeof h?.cwd !== "string" || !h.cwd) return null;
|
|
2482
2633
|
return h;
|
|
@@ -2486,10 +2637,10 @@ function readHandle(tool) {
|
|
|
2486
2637
|
}
|
|
2487
2638
|
function writeHandle(tool, handle) {
|
|
2488
2639
|
try {
|
|
2489
|
-
|
|
2640
|
+
mkdirSync11(handleDir(), { recursive: true });
|
|
2490
2641
|
const tmp = handlePath(tool) + ".tmp";
|
|
2491
|
-
|
|
2492
|
-
|
|
2642
|
+
writeFileSync10(tmp, JSON.stringify(handle), "utf8");
|
|
2643
|
+
renameSync8(tmp, handlePath(tool));
|
|
2493
2644
|
} catch {
|
|
2494
2645
|
}
|
|
2495
2646
|
}
|
|
@@ -2542,11 +2693,11 @@ function failureMessage(reason) {
|
|
|
2542
2693
|
}
|
|
2543
2694
|
|
|
2544
2695
|
// src/daemon/seen.ts
|
|
2545
|
-
import { mkdirSync as
|
|
2546
|
-
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";
|
|
2547
2698
|
var SEEN_CAPACITY = 200;
|
|
2548
2699
|
function seenPath() {
|
|
2549
|
-
return
|
|
2700
|
+
return join15(deskbuddyHome(), "seen.json");
|
|
2550
2701
|
}
|
|
2551
2702
|
var SeenIds = class _SeenIds {
|
|
2552
2703
|
#ids = [];
|
|
@@ -2577,7 +2728,7 @@ var SeenIds = class _SeenIds {
|
|
|
2577
2728
|
}
|
|
2578
2729
|
static load() {
|
|
2579
2730
|
try {
|
|
2580
|
-
const raw = JSON.parse(
|
|
2731
|
+
const raw = JSON.parse(readFileSync12(seenPath(), "utf8"));
|
|
2581
2732
|
return new _SeenIds(Array.isArray(raw?.ids) ? raw.ids.filter((x) => typeof x === "string") : []);
|
|
2582
2733
|
} catch {
|
|
2583
2734
|
return new _SeenIds();
|
|
@@ -2585,10 +2736,10 @@ var SeenIds = class _SeenIds {
|
|
|
2585
2736
|
}
|
|
2586
2737
|
save() {
|
|
2587
2738
|
try {
|
|
2588
|
-
|
|
2739
|
+
mkdirSync12(deskbuddyHome(), { recursive: true });
|
|
2589
2740
|
const tmp = seenPath() + ".tmp";
|
|
2590
|
-
|
|
2591
|
-
|
|
2741
|
+
writeFileSync11(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
|
|
2742
|
+
renameSync9(tmp, seenPath());
|
|
2592
2743
|
} catch {
|
|
2593
2744
|
}
|
|
2594
2745
|
}
|
|
@@ -3250,30 +3401,236 @@ var CodexWatch = class {
|
|
|
3250
3401
|
};
|
|
3251
3402
|
|
|
3252
3403
|
// src/core/codexRemote.ts
|
|
3253
|
-
import { mkdirSync as
|
|
3254
|
-
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";
|
|
3255
3406
|
function codexRemotePath() {
|
|
3256
|
-
return
|
|
3407
|
+
return join16(deskbuddyHome(), "codex-remote.json");
|
|
3257
3408
|
}
|
|
3258
3409
|
function writeCodexRemote(threads, pid = process.pid, now = Date.now()) {
|
|
3259
3410
|
try {
|
|
3260
|
-
|
|
3411
|
+
mkdirSync13(deskbuddyHome(), { recursive: true });
|
|
3261
3412
|
const p = codexRemotePath();
|
|
3262
|
-
|
|
3263
|
-
|
|
3413
|
+
writeFileSync12(p + ".tmp", JSON.stringify({ pid, threads: [...threads], at: now }), "utf8");
|
|
3414
|
+
renameSync10(p + ".tmp", p);
|
|
3264
3415
|
} catch {
|
|
3265
3416
|
}
|
|
3266
3417
|
}
|
|
3267
3418
|
function removeCodexRemote() {
|
|
3268
3419
|
try {
|
|
3269
|
-
|
|
3420
|
+
rmSync5(codexRemotePath(), { force: true });
|
|
3270
3421
|
} catch {
|
|
3271
3422
|
}
|
|
3272
3423
|
}
|
|
3273
3424
|
|
|
3274
3425
|
// src/wrap/channel.ts
|
|
3275
|
-
import {
|
|
3426
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
3427
|
+
import { chmodSync as chmodSync3, rmSync as rmSync7 } from "fs";
|
|
3276
3428
|
import { connect, createServer as createServer2 } from "net";
|
|
3429
|
+
|
|
3430
|
+
// src/core/wake.ts
|
|
3431
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
3432
|
+
import { createHash as createHash3, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3433
|
+
import { chmodSync as chmodSync2, closeSync as closeSync2, mkdirSync as mkdirSync14, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync13, readSync as readSync2, renameSync as renameSync11, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync13 } from "fs";
|
|
3434
|
+
import { tmpdir } from "os";
|
|
3435
|
+
import { join as join17 } from "path";
|
|
3436
|
+
function wakeDir(home = deskbuddyHome()) {
|
|
3437
|
+
return join17(home, "wake");
|
|
3438
|
+
}
|
|
3439
|
+
function marksDir(home = deskbuddyHome()) {
|
|
3440
|
+
return join17(wakeDir(home), "marks");
|
|
3441
|
+
}
|
|
3442
|
+
function safeSid(sessionId) {
|
|
3443
|
+
return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
|
|
3444
|
+
}
|
|
3445
|
+
function homeTag(home) {
|
|
3446
|
+
return createHash3("sha1").update(home).digest("hex").slice(0, 16);
|
|
3447
|
+
}
|
|
3448
|
+
function wakeChannelPath(platform = process.platform, home = deskbuddyHome()) {
|
|
3449
|
+
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-wake-${homeTag(home)}`;
|
|
3450
|
+
const primary = join17(wakeDir(home), "wake.sock");
|
|
3451
|
+
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
3452
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
3453
|
+
return join17(tmpdir(), `deskbuddy-${uid}`, `wake-${homeTag(home)}.sock`);
|
|
3454
|
+
}
|
|
3455
|
+
function pidExists(pid) {
|
|
3456
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
3457
|
+
try {
|
|
3458
|
+
process.kill(pid, 0);
|
|
3459
|
+
return true;
|
|
3460
|
+
} catch {
|
|
3461
|
+
return false;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
function writeWakeMark(sessionId, agentPid, now = Date.now()) {
|
|
3465
|
+
const safe = safeSid(sessionId);
|
|
3466
|
+
if (!safe) return;
|
|
3467
|
+
try {
|
|
3468
|
+
mkdirSync14(marksDir(), { recursive: true, mode: 448 });
|
|
3469
|
+
const file = join17(marksDir(), `${safe}.json`);
|
|
3470
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
3471
|
+
writeFileSync13(tmp, JSON.stringify({ agentPid: agentPid ?? 0, daemonPid: process.pid, at: now }), { encoding: "utf8", mode: 384 });
|
|
3472
|
+
renameSync11(tmp, file);
|
|
3473
|
+
} catch {
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
function removeWakeMark(sessionId) {
|
|
3477
|
+
const safe = safeSid(sessionId);
|
|
3478
|
+
if (!safe) return;
|
|
3479
|
+
try {
|
|
3480
|
+
rmSync6(join17(marksDir(), `${safe}.json`), { force: true });
|
|
3481
|
+
} catch {
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
function clearWakeMarks() {
|
|
3485
|
+
let names = [];
|
|
3486
|
+
try {
|
|
3487
|
+
names = readdirSync3(marksDir());
|
|
3488
|
+
} catch {
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3491
|
+
for (const n of names) {
|
|
3492
|
+
try {
|
|
3493
|
+
rmSync6(join17(marksDir(), n), { force: true });
|
|
3494
|
+
} catch {
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
function isWakeMarked(sessionId, alive = pidExists) {
|
|
3499
|
+
const safe = safeSid(sessionId);
|
|
3500
|
+
if (!safe) return false;
|
|
3501
|
+
try {
|
|
3502
|
+
const m = JSON.parse(readFileSync13(join17(marksDir(), `${safe}.json`), "utf8"));
|
|
3503
|
+
const daemonPid = Number(m?.daemonPid) || 0;
|
|
3504
|
+
if (!daemonPid || !alive(daemonPid)) return false;
|
|
3505
|
+
const pid = Number(m?.agentPid) || 0;
|
|
3506
|
+
return pid > 0 ? alive(pid) : true;
|
|
3507
|
+
} catch {
|
|
3508
|
+
return false;
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
function withWake(states, alive) {
|
|
3512
|
+
return states.map((s) => s.remoteWindow === true || !isWakeMarked(s.sessionId, alive) ? s : { ...s, remoteWindow: true });
|
|
3513
|
+
}
|
|
3514
|
+
function wakeSecretPath(home = deskbuddyHome()) {
|
|
3515
|
+
return join17(wakeDir(home), "secret");
|
|
3516
|
+
}
|
|
3517
|
+
function writeWakeSecret(hex, platform = process.platform) {
|
|
3518
|
+
const file = wakeSecretPath();
|
|
3519
|
+
mkdirSync14(wakeDir(), { recursive: true, mode: 448 });
|
|
3520
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
3521
|
+
writeFileSync13(tmp, hex, { encoding: "utf8", mode: 384 });
|
|
3522
|
+
if (platform === "win32") {
|
|
3523
|
+
try {
|
|
3524
|
+
execFileSync7("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
|
|
3525
|
+
} catch {
|
|
3526
|
+
renameSync11(tmp, file);
|
|
3527
|
+
return "\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u6587\u4EF6\u7684\u6743\u9650\u6CA1\u80FD\u6536\u7D27\uFF08icacls \u5931\u8D25\uFF09";
|
|
3528
|
+
}
|
|
3529
|
+
} else {
|
|
3530
|
+
chmodSync2(tmp, 384);
|
|
3531
|
+
}
|
|
3532
|
+
renameSync11(tmp, file);
|
|
3533
|
+
return "";
|
|
3534
|
+
}
|
|
3535
|
+
function restrictToUser(file, platform = process.platform) {
|
|
3536
|
+
if (platform !== "win32") return true;
|
|
3537
|
+
try {
|
|
3538
|
+
execFileSync7("icacls", [file, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
|
|
3539
|
+
return true;
|
|
3540
|
+
} catch {
|
|
3541
|
+
return false;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
function currentUserSid() {
|
|
3545
|
+
try {
|
|
3546
|
+
const out = execFileSync7("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
|
|
3547
|
+
const m = /"(S-1-[0-9-]+)"/.exec(out);
|
|
3548
|
+
if (m) return `*${m[1]}`;
|
|
3549
|
+
} catch {
|
|
3550
|
+
}
|
|
3551
|
+
return String(process.env.USERNAME);
|
|
3552
|
+
}
|
|
3553
|
+
function canonicalJson(v) {
|
|
3554
|
+
if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
|
|
3555
|
+
if (v && typeof v === "object") {
|
|
3556
|
+
const o = v;
|
|
3557
|
+
return "{" + Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => JSON.stringify(k) + ":" + canonicalJson(o[k])).join(",") + "}";
|
|
3558
|
+
}
|
|
3559
|
+
return JSON.stringify(v === void 0 ? null : v);
|
|
3560
|
+
}
|
|
3561
|
+
function wakeMac(secret, ...parts) {
|
|
3562
|
+
return createHmac2("sha256", secret).update(parts.join("|"), "utf8").digest("hex");
|
|
3563
|
+
}
|
|
3564
|
+
var MAC_RE = /^[0-9a-f]{64}$/;
|
|
3565
|
+
function macEqual(a, b) {
|
|
3566
|
+
if (typeof a !== "string" || typeof b !== "string" || !MAC_RE.test(a) || !MAC_RE.test(b)) return false;
|
|
3567
|
+
return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
3568
|
+
}
|
|
3569
|
+
var WAKE_MARK_RE = /\[DeskBuddy#([0-9A-Za-z_-]{1,64})(?::[0-9a-f]{32})?\]/;
|
|
3570
|
+
var INTERRUPT_RE = /^\[Request interrupted by user/;
|
|
3571
|
+
var TRANSCRIPT_TAIL2 = 256 * 1024;
|
|
3572
|
+
function userText(content) {
|
|
3573
|
+
if (typeof content === "string") return content;
|
|
3574
|
+
if (!Array.isArray(content)) return "";
|
|
3575
|
+
return content.filter((x) => x && x.type === "text").map((x) => String(x.text ?? "")).join("\n");
|
|
3576
|
+
}
|
|
3577
|
+
function analyzeTranscript(file) {
|
|
3578
|
+
if (!file) return null;
|
|
3579
|
+
let fd;
|
|
3580
|
+
try {
|
|
3581
|
+
const size = statSync4(file).size;
|
|
3582
|
+
const len = Math.min(size, TRANSCRIPT_TAIL2);
|
|
3583
|
+
const buf = Buffer.alloc(len);
|
|
3584
|
+
fd = openSync2(file, "r");
|
|
3585
|
+
readSync2(fd, buf, 0, len, size - len);
|
|
3586
|
+
const lines2 = buf.toString("utf8").split("\n");
|
|
3587
|
+
let seg = false;
|
|
3588
|
+
let reply = null;
|
|
3589
|
+
let humanAfter = false;
|
|
3590
|
+
let interrupted = null;
|
|
3591
|
+
for (let i = lines2.length - 1; i >= 0; i--) {
|
|
3592
|
+
const line = lines2[i];
|
|
3593
|
+
if (line.indexOf('"assistant"') === -1 && line.indexOf('"user"') === -1) continue;
|
|
3594
|
+
let o;
|
|
3595
|
+
try {
|
|
3596
|
+
o = JSON.parse(line);
|
|
3597
|
+
} catch {
|
|
3598
|
+
continue;
|
|
3599
|
+
}
|
|
3600
|
+
if (!o || !o.message) continue;
|
|
3601
|
+
if (o.type === "assistant") {
|
|
3602
|
+
const t = userText(o.message.content).trim();
|
|
3603
|
+
if (t) {
|
|
3604
|
+
seg = true;
|
|
3605
|
+
if (reply === null) reply = t;
|
|
3606
|
+
}
|
|
3607
|
+
continue;
|
|
3608
|
+
}
|
|
3609
|
+
if (o.type !== "user") continue;
|
|
3610
|
+
const text = userText(o.message.content);
|
|
3611
|
+
if (!text) continue;
|
|
3612
|
+
const m = WAKE_MARK_RE.exec(text);
|
|
3613
|
+
if (!m && (o.isMeta === true || text.trim().startsWith("<"))) continue;
|
|
3614
|
+
if (m) return { id: m[1], answered: seg, humanAfter, interrupted: interrupted === true, reply: reply ?? "" };
|
|
3615
|
+
if (interrupted === null) interrupted = INTERRUPT_RE.test(text.trim());
|
|
3616
|
+
humanAfter = true;
|
|
3617
|
+
seg = false;
|
|
3618
|
+
reply = null;
|
|
3619
|
+
}
|
|
3620
|
+
return interrupted === null ? null : { id: "", answered: false, humanAfter, interrupted, reply: "" };
|
|
3621
|
+
} catch {
|
|
3622
|
+
return null;
|
|
3623
|
+
} finally {
|
|
3624
|
+
if (fd !== void 0) {
|
|
3625
|
+
try {
|
|
3626
|
+
closeSync2(fd);
|
|
3627
|
+
} catch {
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
|
|
3633
|
+
// src/wrap/channel.ts
|
|
3277
3634
|
var MAX_LINE_BYTES = 64 * 1024;
|
|
3278
3635
|
function lines(sock, onLine) {
|
|
3279
3636
|
let buf = "";
|
|
@@ -3294,46 +3651,81 @@ function lines(sock, onLine) {
|
|
|
3294
3651
|
}
|
|
3295
3652
|
function parseRequest(line) {
|
|
3296
3653
|
let m;
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3654
|
+
if (typeof line === "string") {
|
|
3655
|
+
try {
|
|
3656
|
+
m = JSON.parse(line);
|
|
3657
|
+
} catch {
|
|
3658
|
+
return null;
|
|
3659
|
+
}
|
|
3660
|
+
} else {
|
|
3661
|
+
m = line;
|
|
3301
3662
|
}
|
|
3302
3663
|
if (!m || typeof m !== "object") return null;
|
|
3303
3664
|
const instructionId = typeof m.instructionId === "string" ? m.instructionId : "";
|
|
3304
3665
|
const sessionId = typeof m.sessionId === "string" ? m.sessionId : "";
|
|
3305
|
-
if (!isValidId(instructionId) || !isValidId(sessionId)) return null;
|
|
3666
|
+
if (!isValidId(instructionId) || sessionId !== "" && !isValidId(sessionId)) return null;
|
|
3306
3667
|
const text = sanitizeText(typeof m.text === "string" ? m.text : "");
|
|
3307
3668
|
if (!text || text.length > MAX_TEXT_LENGTH) return null;
|
|
3308
3669
|
const expireAt = typeof m.expireAt === "number" && Number.isFinite(m.expireAt) ? m.expireAt : 0;
|
|
3309
3670
|
return { instructionId, sessionId, text, expireAt };
|
|
3310
3671
|
}
|
|
3311
|
-
|
|
3672
|
+
var HEX32 = /^[0-9a-f]{32}$/;
|
|
3673
|
+
function replyMac(secret, nonceC, r) {
|
|
3674
|
+
return wakeMac(secret, "r", nonceC, r.instructionId, r.status, r.reason ?? "");
|
|
3675
|
+
}
|
|
3676
|
+
function startChannelServer(path, secret, onRequest, platform = process.platform) {
|
|
3312
3677
|
return new Promise((resolve5) => {
|
|
3313
3678
|
const server = createServer2((sock) => {
|
|
3314
3679
|
let open = true;
|
|
3680
|
+
let phase = "hello";
|
|
3681
|
+
let nonceC = "";
|
|
3682
|
+
let nonceS = "";
|
|
3315
3683
|
sock.on("error", () => {
|
|
3316
3684
|
});
|
|
3317
3685
|
sock.on("close", () => open = false);
|
|
3318
|
-
const
|
|
3686
|
+
const authTimer = setTimeout(() => {
|
|
3687
|
+
if (phase === "hello") sock.destroy();
|
|
3688
|
+
}, 5e3);
|
|
3689
|
+
authTimer.unref?.();
|
|
3690
|
+
const raw = (m) => {
|
|
3319
3691
|
if (sock.destroyed || !sock.writable) return;
|
|
3320
3692
|
try {
|
|
3321
|
-
sock.write(JSON.stringify(
|
|
3693
|
+
sock.write(JSON.stringify(m) + "\n");
|
|
3322
3694
|
} catch {
|
|
3323
3695
|
}
|
|
3324
3696
|
};
|
|
3697
|
+
const reply = (r) => raw({ ...r, mac: replyMac(secret, nonceC, r) });
|
|
3325
3698
|
lines(sock, (line) => {
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3699
|
+
try {
|
|
3700
|
+
const m = JSON.parse(line);
|
|
3701
|
+
if (!m || typeof m !== "object") throw new Error("bad frame");
|
|
3702
|
+
if (phase === "hello") {
|
|
3703
|
+
if (m.t !== "hello" || typeof m.nonceC !== "string" || !HEX32.test(m.nonceC)) throw new Error("bad hello");
|
|
3704
|
+
nonceC = m.nonceC;
|
|
3705
|
+
nonceS = randomBytes5(16).toString("hex");
|
|
3706
|
+
phase = "req";
|
|
3707
|
+
clearTimeout(authTimer);
|
|
3708
|
+
raw({ t: "auth", nonceS, mac: wakeMac(secret, "s", nonceC, nonceS) });
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
if (phase !== "req") return;
|
|
3712
|
+
const { mac, ...body } = m;
|
|
3713
|
+
if (!macEqual(mac, wakeMac(secret, "c", nonceS, canonicalJson(body)))) throw new Error("bad mac");
|
|
3714
|
+
const req2 = parseRequest(body);
|
|
3715
|
+
if (!req2) {
|
|
3716
|
+
reply({ instructionId: typeof body.instructionId === "string" ? body.instructionId : "", status: "rejected", reason: "bad_request" });
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
phase = "done";
|
|
3720
|
+
onRequest(req2, reply, () => open);
|
|
3721
|
+
} catch {
|
|
3722
|
+
sock.destroy();
|
|
3330
3723
|
}
|
|
3331
|
-
onRequest(req2, reply, () => open);
|
|
3332
3724
|
});
|
|
3333
3725
|
});
|
|
3334
3726
|
if (platform !== "win32") {
|
|
3335
3727
|
try {
|
|
3336
|
-
|
|
3728
|
+
rmSync7(path, { force: true });
|
|
3337
3729
|
} catch {
|
|
3338
3730
|
}
|
|
3339
3731
|
}
|
|
@@ -3349,7 +3741,7 @@ function startChannelServer(path, onRequest, platform = process.platform) {
|
|
|
3349
3741
|
restore();
|
|
3350
3742
|
if (platform !== "win32") {
|
|
3351
3743
|
try {
|
|
3352
|
-
|
|
3744
|
+
chmodSync3(path, 384);
|
|
3353
3745
|
} catch {
|
|
3354
3746
|
}
|
|
3355
3747
|
}
|
|
@@ -3366,17 +3758,23 @@ function closeChannelServer(server, path, platform = process.platform) {
|
|
|
3366
3758
|
}
|
|
3367
3759
|
if (platform !== "win32") {
|
|
3368
3760
|
try {
|
|
3369
|
-
|
|
3761
|
+
rmSync7(path, { force: true });
|
|
3370
3762
|
} catch {
|
|
3371
3763
|
}
|
|
3372
3764
|
}
|
|
3373
3765
|
}
|
|
3374
3766
|
var FINAL = /* @__PURE__ */ new Set(["started", "rejected", "failed", "not_here"]);
|
|
3375
|
-
function sendToWindow(path, req2, onReply, opts = {}) {
|
|
3767
|
+
function sendToWindow(path, secret, req2, onReply, opts = {}) {
|
|
3376
3768
|
return new Promise((resolve5) => {
|
|
3769
|
+
if (!secret) {
|
|
3770
|
+
resolve5("fallback");
|
|
3771
|
+
return;
|
|
3772
|
+
}
|
|
3377
3773
|
let settled = false;
|
|
3378
3774
|
let handled = false;
|
|
3379
3775
|
let done = false;
|
|
3776
|
+
const nonceC = randomBytes5(16).toString("hex");
|
|
3777
|
+
let nonceS = "";
|
|
3380
3778
|
const settle = (v) => {
|
|
3381
3779
|
if (settled) return;
|
|
3382
3780
|
settled = true;
|
|
@@ -3409,16 +3807,33 @@ function sendToWindow(path, req2, onReply, opts = {}) {
|
|
|
3409
3807
|
}
|
|
3410
3808
|
});
|
|
3411
3809
|
sock.on("connect", () => {
|
|
3412
|
-
sock.write(JSON.stringify(
|
|
3810
|
+
sock.write(JSON.stringify({ t: "hello", nonceC }) + "\n");
|
|
3413
3811
|
});
|
|
3414
3812
|
lines(sock, (line) => {
|
|
3415
|
-
let
|
|
3813
|
+
let m;
|
|
3416
3814
|
try {
|
|
3417
|
-
|
|
3815
|
+
m = JSON.parse(line);
|
|
3418
3816
|
} catch {
|
|
3817
|
+
sock.destroy();
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
if (!nonceS) {
|
|
3821
|
+
if (m.t !== "auth" || typeof m.nonceS !== "string" || !HEX32.test(m.nonceS) || !macEqual(m.mac, wakeMac(secret, "s", nonceC, m.nonceS))) {
|
|
3822
|
+
sock.destroy();
|
|
3823
|
+
return;
|
|
3824
|
+
}
|
|
3825
|
+
nonceS = m.nonceS;
|
|
3826
|
+
const body = { ...req2 };
|
|
3827
|
+
sock.write(JSON.stringify({ ...body, mac: wakeMac(secret, "c", nonceS, canonicalJson(body)) }) + "\n");
|
|
3419
3828
|
return;
|
|
3420
3829
|
}
|
|
3830
|
+
const { mac, ...rest } = m;
|
|
3831
|
+
const r = rest;
|
|
3421
3832
|
if (r.instructionId !== req2.instructionId) return;
|
|
3833
|
+
if (!macEqual(mac, replyMac(secret, nonceC, r))) {
|
|
3834
|
+
sock.destroy();
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3422
3837
|
if (!settled) {
|
|
3423
3838
|
if (r.status === "ack") {
|
|
3424
3839
|
settle("handled");
|
|
@@ -3440,40 +3855,85 @@ function sendToWindow(path, req2, onReply, opts = {}) {
|
|
|
3440
3855
|
});
|
|
3441
3856
|
}
|
|
3442
3857
|
|
|
3858
|
+
// src/wrap/screen.ts
|
|
3859
|
+
var ESC = "\x1B";
|
|
3860
|
+
var PASTE_BEGIN = ESC + "[200~";
|
|
3861
|
+
var PASTE_END = ESC + "[201~";
|
|
3862
|
+
function sanitize(text) {
|
|
3863
|
+
return String(text).replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
|
|
3864
|
+
}
|
|
3865
|
+
var MAX_INJECT_CHARS = 800;
|
|
3866
|
+
var TEXT_TOO_LONG = "text_too_long";
|
|
3867
|
+
function oneLine(text) {
|
|
3868
|
+
return sanitize(text).replace(/\s*\n\s*/g, " ").trim();
|
|
3869
|
+
}
|
|
3870
|
+
function tooLongToInject(text) {
|
|
3871
|
+
return oneLine(text).length > MAX_INJECT_CHARS;
|
|
3872
|
+
}
|
|
3873
|
+
function prepare(text) {
|
|
3874
|
+
return " " + oneLine(text);
|
|
3875
|
+
}
|
|
3876
|
+
var PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
|
|
3877
|
+
function inspectLines(lines2) {
|
|
3878
|
+
const text = lines2.join("\n");
|
|
3879
|
+
const permission = PERMISSION_RE.test(text);
|
|
3880
|
+
const rules = [];
|
|
3881
|
+
lines2.forEach((l, i) => {
|
|
3882
|
+
if (/^─{20,}/.test(l.trim())) rules.push(i);
|
|
3883
|
+
});
|
|
3884
|
+
let input = null;
|
|
3885
|
+
if (rules.length >= 2) {
|
|
3886
|
+
const a = rules[rules.length - 2];
|
|
3887
|
+
const b = rules[rules.length - 1];
|
|
3888
|
+
input = lines2.slice(a + 1, b).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
|
|
3889
|
+
}
|
|
3890
|
+
return { permission, input };
|
|
3891
|
+
}
|
|
3892
|
+
function boxMatches(view, body) {
|
|
3893
|
+
if (view.input === null || view.permission) return false;
|
|
3894
|
+
const flat = (x) => x.replace(/\s+/g, "");
|
|
3895
|
+
return flat(view.input) === flat(body) || /\[Pasted text #\d+/.test(view.input);
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3443
3898
|
// src/core/wrapWindows.ts
|
|
3444
|
-
import { chmodSync as
|
|
3445
|
-
import { tmpdir } from "os";
|
|
3446
|
-
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";
|
|
3447
3902
|
var WINDOW_ID_RE = /^[a-f0-9]{32}$/;
|
|
3448
3903
|
function windowsDir() {
|
|
3449
|
-
return
|
|
3904
|
+
return join18(deskbuddyHome(), "windows");
|
|
3450
3905
|
}
|
|
3451
3906
|
function bySessionDir() {
|
|
3452
|
-
return
|
|
3907
|
+
return join18(windowsDir(), "by-session");
|
|
3453
3908
|
}
|
|
3454
|
-
function
|
|
3909
|
+
function safeSid2(sessionId) {
|
|
3455
3910
|
return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
|
|
3456
3911
|
}
|
|
3457
3912
|
function ensureDir(dir) {
|
|
3458
|
-
|
|
3913
|
+
mkdirSync15(dir, { recursive: true, mode: 448 });
|
|
3459
3914
|
}
|
|
3460
3915
|
function atomicWrite(file, text) {
|
|
3461
3916
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
3462
|
-
|
|
3463
|
-
|
|
3917
|
+
writeFileSync14(tmp, text, { encoding: "utf8", mode: 384 });
|
|
3918
|
+
renameSync12(tmp, file);
|
|
3464
3919
|
}
|
|
3465
3920
|
function windowFile(windowId) {
|
|
3466
|
-
return
|
|
3921
|
+
return join18(windowsDir(), `${windowId}.json`);
|
|
3467
3922
|
}
|
|
3468
3923
|
function writeWindow(windowId, rec) {
|
|
3469
3924
|
if (!WINDOW_ID_RE.test(windowId)) return;
|
|
3470
3925
|
ensureDir(windowsDir());
|
|
3471
3926
|
atomicWrite(windowFile(windowId), JSON.stringify(rec));
|
|
3927
|
+
restrictToUser(windowFile(windowId));
|
|
3928
|
+
}
|
|
3929
|
+
function windowSecret(windowId) {
|
|
3930
|
+
const s = readWindow(windowId)?.secret;
|
|
3931
|
+
return typeof s === "string" && /^[0-9a-f]{64}$/.test(s) ? Buffer.from(s, "hex") : null;
|
|
3472
3932
|
}
|
|
3473
3933
|
function readWindow(windowId) {
|
|
3474
3934
|
if (!WINDOW_ID_RE.test(windowId)) return null;
|
|
3475
3935
|
try {
|
|
3476
|
-
const r = JSON.parse(
|
|
3936
|
+
const r = JSON.parse(readFileSync14(windowFile(windowId), "utf8"));
|
|
3477
3937
|
return r && typeof r.pid === "number" ? r : null;
|
|
3478
3938
|
} catch {
|
|
3479
3939
|
return null;
|
|
@@ -3483,41 +3943,41 @@ function removeWindow(windowId) {
|
|
|
3483
3943
|
if (!WINDOW_ID_RE.test(windowId)) return;
|
|
3484
3944
|
for (const f of [windowFile(windowId), lastEventFile(windowId)]) {
|
|
3485
3945
|
try {
|
|
3486
|
-
|
|
3946
|
+
rmSync8(f, { force: true });
|
|
3487
3947
|
} catch {
|
|
3488
3948
|
}
|
|
3489
3949
|
}
|
|
3490
3950
|
let names = [];
|
|
3491
3951
|
try {
|
|
3492
|
-
names =
|
|
3952
|
+
names = readdirSync4(bySessionDir());
|
|
3493
3953
|
} catch {
|
|
3494
3954
|
return;
|
|
3495
3955
|
}
|
|
3496
3956
|
for (const n of names) {
|
|
3497
|
-
const f =
|
|
3957
|
+
const f = join18(bySessionDir(), n);
|
|
3498
3958
|
try {
|
|
3499
|
-
if (
|
|
3959
|
+
if (readFileSync14(f, "utf8").trim() === windowId) rmSync8(f, { force: true });
|
|
3500
3960
|
} catch {
|
|
3501
3961
|
}
|
|
3502
3962
|
}
|
|
3503
3963
|
}
|
|
3504
3964
|
function readBySession(sessionId) {
|
|
3505
|
-
const safe =
|
|
3965
|
+
const safe = safeSid2(sessionId);
|
|
3506
3966
|
if (!safe) return null;
|
|
3507
3967
|
try {
|
|
3508
|
-
const id =
|
|
3968
|
+
const id = readFileSync14(join18(bySessionDir(), safe), "utf8").trim();
|
|
3509
3969
|
return WINDOW_ID_RE.test(id) ? id : null;
|
|
3510
3970
|
} catch {
|
|
3511
3971
|
return null;
|
|
3512
3972
|
}
|
|
3513
3973
|
}
|
|
3514
3974
|
function lastEventFile(windowId) {
|
|
3515
|
-
return
|
|
3975
|
+
return join18(windowsDir(), `${windowId}.last`);
|
|
3516
3976
|
}
|
|
3517
3977
|
function readLastEvent(windowId) {
|
|
3518
3978
|
if (!WINDOW_ID_RE.test(windowId)) return null;
|
|
3519
3979
|
try {
|
|
3520
|
-
const e = JSON.parse(
|
|
3980
|
+
const e = JSON.parse(readFileSync14(lastEventFile(windowId), "utf8"));
|
|
3521
3981
|
return e && typeof e.ev === "string" && typeof e.at === "number" ? { ev: e.ev, sid: String(e.sid ?? ""), at: e.at } : null;
|
|
3522
3982
|
} catch {
|
|
3523
3983
|
return null;
|
|
@@ -3526,18 +3986,18 @@ function readLastEvent(windowId) {
|
|
|
3526
3986
|
function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
|
|
3527
3987
|
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
|
|
3528
3988
|
const name = `${windowId.slice(0, 16)}.sock`;
|
|
3529
|
-
const primary =
|
|
3989
|
+
const primary = join18(home, "win", name);
|
|
3530
3990
|
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
3531
3991
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
3532
|
-
return
|
|
3992
|
+
return join18(tmpdir2(), `deskbuddy-${uid}`, name);
|
|
3533
3993
|
}
|
|
3534
3994
|
function ensureChannelDir(sockPath) {
|
|
3535
|
-
const dir =
|
|
3995
|
+
const dir = join18(sockPath, "..");
|
|
3536
3996
|
try {
|
|
3537
|
-
|
|
3538
|
-
const st =
|
|
3997
|
+
mkdirSync15(dir, { recursive: true, mode: 448 });
|
|
3998
|
+
const st = statSync5(dir);
|
|
3539
3999
|
if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
|
|
3540
|
-
|
|
4000
|
+
chmodSync4(dir, 448);
|
|
3541
4001
|
return true;
|
|
3542
4002
|
} catch {
|
|
3543
4003
|
return false;
|
|
@@ -3553,23 +4013,595 @@ function windowForSession(sessionId) {
|
|
|
3553
4013
|
return id;
|
|
3554
4014
|
}
|
|
3555
4015
|
|
|
3556
|
-
// src/
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
var
|
|
3560
|
-
var
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
return
|
|
3566
|
-
}
|
|
3567
|
-
function
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
}
|
|
4016
|
+
// src/core/launch.ts
|
|
4017
|
+
import { mkdirSync as mkdirSync16, readFileSync as readFileSync15, renameSync as renameSync13, rmSync as rmSync9, writeFileSync as writeFileSync15 } from "fs";
|
|
4018
|
+
import { join as join19 } from "path";
|
|
4019
|
+
var RUN_ID_RE = /^[a-f0-9]{32}$/;
|
|
4020
|
+
var SESSION_UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
4021
|
+
function launchDir(home = deskbuddyHome()) {
|
|
4022
|
+
return join19(home, "launch");
|
|
4023
|
+
}
|
|
4024
|
+
function planFile(runId) {
|
|
4025
|
+
return join19(launchDir(), `${runId}.json`);
|
|
4026
|
+
}
|
|
4027
|
+
function writeLaunch(runId, plan) {
|
|
4028
|
+
if (!RUN_ID_RE.test(runId)) throw new Error("bad runId");
|
|
4029
|
+
if (plan.resume && !SESSION_UUID_RE.test(plan.resume)) throw new Error("bad session id");
|
|
4030
|
+
mkdirSync16(launchDir(), { recursive: true, mode: 448 });
|
|
4031
|
+
const tmp = `${planFile(runId)}.${process.pid}.tmp`;
|
|
4032
|
+
writeFileSync15(tmp, JSON.stringify(plan), { encoding: "utf8", mode: 384 });
|
|
4033
|
+
restrictToUser(tmp);
|
|
4034
|
+
renameSync13(tmp, planFile(runId));
|
|
4035
|
+
}
|
|
4036
|
+
function claimLaunch(runId) {
|
|
4037
|
+
if (!RUN_ID_RE.test(runId)) return null;
|
|
4038
|
+
const claimed = join19(launchDir(), `${runId}.${process.pid}.claimed`);
|
|
4039
|
+
try {
|
|
4040
|
+
renameSync13(planFile(runId), claimed);
|
|
4041
|
+
} catch {
|
|
4042
|
+
return null;
|
|
4043
|
+
}
|
|
4044
|
+
try {
|
|
4045
|
+
const p = JSON.parse(readFileSync15(claimed, "utf8"));
|
|
4046
|
+
if (!p || typeof p.cwd !== "string" || typeof p.resume !== "string") return null;
|
|
4047
|
+
if (p.resume && !SESSION_UUID_RE.test(p.resume)) return null;
|
|
4048
|
+
return p;
|
|
4049
|
+
} catch {
|
|
4050
|
+
return null;
|
|
4051
|
+
} finally {
|
|
4052
|
+
try {
|
|
4053
|
+
rmSync9(claimed, { force: true });
|
|
4054
|
+
} catch {
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
function launchPending(runId) {
|
|
4059
|
+
try {
|
|
4060
|
+
readFileSync15(planFile(runId));
|
|
4061
|
+
return true;
|
|
4062
|
+
} catch {
|
|
4063
|
+
return false;
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
// src/daemon/openWindow.ts
|
|
4068
|
+
import { execFile as execFile3, execFileSync as execFileSync8, spawn as spawn2 } from "child_process";
|
|
4069
|
+
import { chmodSync as chmodSync5, mkdirSync as mkdirSync17, rmSync as rmSync10, writeFileSync as writeFileSync16 } from "fs";
|
|
4070
|
+
import { userInfo } from "os";
|
|
4071
|
+
import { join as join20 } from "path";
|
|
4072
|
+
function safePath(p) {
|
|
4073
|
+
return /^(?:[A-Za-z]:[\\/]|\/)/.test(p) && !/[\r\n\0"'`$%;]/.test(p);
|
|
4074
|
+
}
|
|
4075
|
+
function checkTarget(t) {
|
|
4076
|
+
if (!RUN_ID_RE.test(t.runId)) throw new Error("bad runId");
|
|
4077
|
+
for (const p of [t.node, t.cli, t.home]) if (!safePath(p)) throw new Error("\u8DEF\u5F84\u91CC\u6709\u4E0D\u80FD\u8FDB\u547D\u4EE4\u884C\u7684\u5B57\u7B26");
|
|
4078
|
+
}
|
|
4079
|
+
var shq = (s) => `'${s}'`;
|
|
4080
|
+
function macCommandScript(t) {
|
|
4081
|
+
checkTarget(t);
|
|
4082
|
+
return `#!/bin/sh
|
|
4083
|
+
exec ${shq(t.node)} ${shq(t.cli)} wrap --launch ${t.runId} --home ${shq(t.home)}
|
|
4084
|
+
`;
|
|
4085
|
+
}
|
|
4086
|
+
function winWtArgs(t) {
|
|
4087
|
+
checkTarget(t);
|
|
4088
|
+
return ["-w", "new", t.node, t.cli, "wrap", "--launch", t.runId, "--home", t.home];
|
|
4089
|
+
}
|
|
4090
|
+
var psq = (s) => `'${s.replace(/'/g, "''")}'`;
|
|
4091
|
+
function winStartProcessScript(t) {
|
|
4092
|
+
checkTarget(t);
|
|
4093
|
+
const arg = (s) => psq(/\s/.test(s) ? `"${s}"` : s);
|
|
4094
|
+
return `Start-Process -FilePath ${psq(t.node)} -ArgumentList @(${[t.cli, "wrap", "--launch", t.runId, "--home", t.home].map(arg).join(",")})`;
|
|
4095
|
+
}
|
|
4096
|
+
function encodePs(script) {
|
|
4097
|
+
return Buffer.from(script, "utf16le").toString("base64");
|
|
4098
|
+
}
|
|
4099
|
+
function guiAvailable(d) {
|
|
4100
|
+
if (d.env.SSH_CONNECTION || d.env.SSH_TTY) return false;
|
|
4101
|
+
if (d.platform === "darwin") {
|
|
4102
|
+
const m = d.macManager().trim();
|
|
4103
|
+
if (m === "Aqua") return true;
|
|
4104
|
+
if (m !== "Background") return false;
|
|
4105
|
+
const me = d.userName?.() ?? "";
|
|
4106
|
+
return !!me && (d.macConsoleUser?.() ?? "").trim() === me;
|
|
4107
|
+
}
|
|
4108
|
+
if (d.platform === "win32") {
|
|
4109
|
+
const s = d.winSessionId();
|
|
4110
|
+
return s !== null && s > 0;
|
|
4111
|
+
}
|
|
4112
|
+
return false;
|
|
4113
|
+
}
|
|
4114
|
+
var defaultGuiDeps = () => ({
|
|
4115
|
+
platform: process.platform,
|
|
4116
|
+
env: process.env,
|
|
4117
|
+
macManager: () => {
|
|
4118
|
+
try {
|
|
4119
|
+
return execFileSync8("launchctl", ["managername"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
|
|
4120
|
+
} catch {
|
|
4121
|
+
return "";
|
|
4122
|
+
}
|
|
4123
|
+
},
|
|
4124
|
+
macConsoleUser: () => {
|
|
4125
|
+
try {
|
|
4126
|
+
return execFileSync8("stat", ["-f", "%Su", "/dev/console"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
|
|
4127
|
+
} catch {
|
|
4128
|
+
return "";
|
|
4129
|
+
}
|
|
4130
|
+
},
|
|
4131
|
+
userName: () => {
|
|
4132
|
+
try {
|
|
4133
|
+
return userInfo().username;
|
|
4134
|
+
} catch {
|
|
4135
|
+
return "";
|
|
4136
|
+
}
|
|
4137
|
+
},
|
|
4138
|
+
winSessionId: () => {
|
|
4139
|
+
try {
|
|
4140
|
+
const out = execFileSync8("tasklist", ["/FI", `PID eq ${process.pid}`, "/FO", "CSV", "/NH"], {
|
|
4141
|
+
encoding: "utf8",
|
|
4142
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4143
|
+
windowsHide: true,
|
|
4144
|
+
timeout: 5e3
|
|
4145
|
+
});
|
|
4146
|
+
const cols = out.trim().split("\n")[0]?.match(/"([^"]*)"/g)?.map((c) => c.slice(1, -1)) ?? [];
|
|
4147
|
+
const n = Number(cols[3]);
|
|
4148
|
+
return Number.isInteger(n) ? n : null;
|
|
4149
|
+
} catch {
|
|
4150
|
+
return null;
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
});
|
|
4154
|
+
var defaultOpenerDeps = (findWt2) => ({
|
|
4155
|
+
platform: process.platform,
|
|
4156
|
+
findWt: findWt2,
|
|
4157
|
+
run: (file, args) => new Promise((resolve5, reject) => {
|
|
4158
|
+
execFile3(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) : resolve5());
|
|
4159
|
+
}),
|
|
4160
|
+
spawnDetached: (file, args) => {
|
|
4161
|
+
const c = spawn2(file, args, { detached: true, stdio: "ignore", shell: false, windowsHide: false });
|
|
4162
|
+
c.on("error", () => {
|
|
4163
|
+
});
|
|
4164
|
+
c.unref();
|
|
4165
|
+
}
|
|
4166
|
+
});
|
|
4167
|
+
async function openTerminal(t, d) {
|
|
4168
|
+
checkTarget(t);
|
|
4169
|
+
if (d.platform === "darwin") {
|
|
4170
|
+
mkdirSync17(launchDir(t.home), { recursive: true, mode: 448 });
|
|
4171
|
+
const file = join20(launchDir(t.home), `${t.runId}.command`);
|
|
4172
|
+
writeFileSync16(file, macCommandScript(t), { encoding: "utf8", mode: 448 });
|
|
4173
|
+
chmodSync5(file, 448);
|
|
4174
|
+
await d.run("open", ["-a", "Terminal", file]);
|
|
4175
|
+
return "terminal";
|
|
4176
|
+
}
|
|
4177
|
+
if (d.platform === "win32") {
|
|
4178
|
+
const wt = d.findWt();
|
|
4179
|
+
if (wt) {
|
|
4180
|
+
d.spawnDetached(wt, winWtArgs(t));
|
|
4181
|
+
return "wt";
|
|
4182
|
+
}
|
|
4183
|
+
await d.run("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", encodePs(winStartProcessScript(t))]);
|
|
4184
|
+
return "powershell";
|
|
4185
|
+
}
|
|
4186
|
+
throw new Error(`\u6682\u4E0D\u652F\u6301\u5728 ${d.platform} \u4E0A\u5F00\u7A97`);
|
|
4187
|
+
}
|
|
4188
|
+
function removeCommandFile(home, runId) {
|
|
4189
|
+
if (!RUN_ID_RE.test(runId)) return;
|
|
4190
|
+
try {
|
|
4191
|
+
rmSync10(join20(launchDir(home), `${runId}.command`), { force: true });
|
|
4192
|
+
} catch {
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
// src/daemon/wake.ts
|
|
4197
|
+
import { randomBytes as randomBytes6 } from "crypto";
|
|
4198
|
+
import { rmSync as rmSync11 } from "fs";
|
|
4199
|
+
import { createServer as createServer3 } from "net";
|
|
4200
|
+
var BUSY_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
|
|
4201
|
+
var STOP_MIN_MS = 1500;
|
|
4202
|
+
var WAKE_MAX_QUEUE_MS = 30 * 60 * 1e3;
|
|
4203
|
+
var WATCH_STALE_MS = 30 * 60 * 1e3;
|
|
4204
|
+
var INTERRUPTED_REASON = "exec_failed";
|
|
4205
|
+
var WakeHub = class {
|
|
4206
|
+
#slots = /* @__PURE__ */ new Map();
|
|
4207
|
+
#d;
|
|
4208
|
+
constructor(deps) {
|
|
4209
|
+
this.#d = {
|
|
4210
|
+
ackTimeoutMs: 3e3,
|
|
4211
|
+
maxQueueMs: WAKE_MAX_QUEUE_MS,
|
|
4212
|
+
flightLimitMs: EXEC_TIMEOUT_MS,
|
|
4213
|
+
watchStaleMs: WATCH_STALE_MS,
|
|
4214
|
+
analyze: analyzeTranscript,
|
|
4215
|
+
...deps
|
|
4216
|
+
};
|
|
4217
|
+
}
|
|
4218
|
+
/** 这个会话有没有登记中的等待者(或刚唤醒、正在跑的那一轮) */
|
|
4219
|
+
has(sessionId) {
|
|
4220
|
+
const s = this.#slots.get(sessionId);
|
|
4221
|
+
return !!s && (!!s.reg || !!s.flight);
|
|
4222
|
+
}
|
|
4223
|
+
/** 交给唤醒路由。返回 false = 这个会话没有等待者,调用方走别的路 */
|
|
4224
|
+
submit(cmd, sessionId) {
|
|
4225
|
+
if (cmd.tool !== "claude_code") return false;
|
|
4226
|
+
const slot = this.#slots.get(sessionId);
|
|
4227
|
+
if (!slot || !slot.reg && !slot.flight) return false;
|
|
4228
|
+
slot.queue.push({ cmd, sessionId, queuedAt: this.#d.now() });
|
|
4229
|
+
this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} \u4EA4\u7ED9\u7A97\u53E3\u5524\u9192\uFF08${slot.flight ? "\u4E0A\u4E00\u6761\u8FD8\u5728\u8DD1" : slot.reg ? "\u6709\u7B49\u5F85\u8005" : "\u65E0\u7B49\u5F85\u8005"}\uFF09`);
|
|
4230
|
+
this.#settle(sessionId, slot);
|
|
4231
|
+
return true;
|
|
4232
|
+
}
|
|
4233
|
+
onHello(peer, h) {
|
|
4234
|
+
const sid = h.sessionId;
|
|
4235
|
+
if (!isValidId(sid)) {
|
|
4236
|
+
peer.close();
|
|
4237
|
+
return;
|
|
4238
|
+
}
|
|
4239
|
+
const slot = this.#slot(sid);
|
|
4240
|
+
if (h.transcript) slot.transcript = h.transcript;
|
|
4241
|
+
if (h.event === "Stop" && !h.re) {
|
|
4242
|
+
slot.lastStopAt = this.#d.now();
|
|
4243
|
+
const f = slot.flight;
|
|
4244
|
+
if (f) this.#onStop(sid, slot, f, h);
|
|
4245
|
+
}
|
|
4246
|
+
if (h.agentPid) {
|
|
4247
|
+
for (const [other, s] of this.#slots) {
|
|
4248
|
+
if (other === sid || !s.reg || s.agentPid !== h.agentPid || s.reg.pending) continue;
|
|
4249
|
+
this.#evict(s);
|
|
4250
|
+
for (const j of s.queue.splice(0)) {
|
|
4251
|
+
j.sessionId = sid;
|
|
4252
|
+
slot.queue.push(j);
|
|
4253
|
+
}
|
|
4254
|
+
this.#settle(other, s);
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
const old = slot.reg;
|
|
4258
|
+
if (old && old.peer !== peer) {
|
|
4259
|
+
if (old.pending || h.re) {
|
|
4260
|
+
peer.send({ t: "dup" });
|
|
4261
|
+
peer.close();
|
|
4262
|
+
this.#settle(sid, slot);
|
|
4263
|
+
return;
|
|
4264
|
+
}
|
|
4265
|
+
this.#evict(slot);
|
|
4266
|
+
}
|
|
4267
|
+
slot.reg = { peer, hello: h };
|
|
4268
|
+
if (h.agentPid) slot.agentPid = h.agentPid;
|
|
4269
|
+
peer.send({ t: "ok" });
|
|
4270
|
+
this.#settle(sid, slot);
|
|
4271
|
+
}
|
|
4272
|
+
/**
|
|
4273
|
+
* 🔴 必须确认是被唤醒的那一轮结束了:一轮结束时 hook.cjs 先写 done,tick 可能抢在这一轮的 Stop 等待者登记前
|
|
4274
|
+
* 把排队指令发给上一个等待者,紧接着登记的 Stop 带的是上一轮的回复(2026-09-30 e2e 实测)
|
|
4275
|
+
*/
|
|
4276
|
+
#onStop(sid, slot, f, h) {
|
|
4277
|
+
const id = f.job.cmd.instructionId;
|
|
4278
|
+
const t = h.tail;
|
|
4279
|
+
if (!t) {
|
|
4280
|
+
if ((h.stopAt ?? this.#d.now()) - f.firedAt < STOP_MIN_MS) {
|
|
4281
|
+
this.#d.log(`\u6307\u4EE4 ${id} \u5DF2\u5524\u9192\uFF0C\u8FD9\u6B21 Stop \u591A\u534A\u662F\u4E0A\u4E00\u8F6E\u7684\uFF0C\u7EE7\u7EED\u7B49`);
|
|
4282
|
+
return;
|
|
4283
|
+
}
|
|
4284
|
+
} else if (t.id !== id || !t.answered && !t.humanAfter) {
|
|
4285
|
+
this.#d.log(`\u6307\u4EE4 ${id} \u5DF2\u5524\u9192\uFF0C\u8FD9\u6B21 Stop \u662F\u4E0A\u4E00\u8F6E\u7684\uFF0C\u7EE7\u7EED\u7B49`);
|
|
4286
|
+
return;
|
|
4287
|
+
} else if (t.humanAfter) {
|
|
4288
|
+
this.#interrupted(sid, slot, f, "Stop \u663E\u793A\u5524\u9192\u4E4B\u540E\u53C8\u6709\u4EBA\u7C7B\u6D88\u606F");
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
4291
|
+
slot.flight = void 0;
|
|
4292
|
+
this.#d.log(`\u6307\u4EE4 ${id} \u7A97\u53E3\u8FD9\u4E00\u8F6E\u5DF2\u7ED3\u675F\uFF0C\u56DE\u590D ${(h.last ?? "").length} \u5B57`);
|
|
4293
|
+
this.#d.done(f.job.cmd, sid, h.last ?? "");
|
|
4294
|
+
}
|
|
4295
|
+
#interrupted(sid, slot, f, why) {
|
|
4296
|
+
slot.flight = void 0;
|
|
4297
|
+
this.#d.log(`\u6307\u4EE4 ${f.job.cmd.instructionId} \u88AB\u5524\u9192\u7684\u90A3\u4E00\u8F6E\u88AB\u6253\u65AD\uFF08${why}\uFF09`);
|
|
4298
|
+
this.#d.report(f.job.cmd, sid, "failed", INTERRUPTED_REASON);
|
|
4299
|
+
}
|
|
4300
|
+
onMessage(peer, m) {
|
|
4301
|
+
const found = this.#find(peer);
|
|
4302
|
+
if (!found) return;
|
|
4303
|
+
const [sid, slot, reg] = found;
|
|
4304
|
+
const p = reg.pending;
|
|
4305
|
+
if (!p || m.id !== p.job.cmd.instructionId) return;
|
|
4306
|
+
clearTimeout(p.timer);
|
|
4307
|
+
reg.pending = void 0;
|
|
4308
|
+
if (m.t === "fired") {
|
|
4309
|
+
const now = this.#d.now();
|
|
4310
|
+
slot.flight = { job: p.job, firedAt: now, paused: 0, lastTick: now };
|
|
4311
|
+
slot.reg = void 0;
|
|
4312
|
+
this.#d.log(`\u6307\u4EE4 ${p.job.cmd.instructionId} \u5DF2\u5524\u9192\u7A97\u53E3 len=${p.job.cmd.text.length}`);
|
|
4313
|
+
this.#d.report(p.job.cmd, sid, "started");
|
|
4314
|
+
} else {
|
|
4315
|
+
slot.reg = void 0;
|
|
4316
|
+
this.#d.log(`\u6307\u4EE4 ${p.job.cmd.instructionId} \u7B49\u5F85\u8005\u8BF4\u7A97\u53E3\u5DF2\u4E0D\u5728\uFF0C\u6539\u8D70\u540E\u53F0`);
|
|
4317
|
+
this.#d.fallback(p.job.cmd);
|
|
4318
|
+
}
|
|
4319
|
+
this.#settle(sid, slot);
|
|
4320
|
+
}
|
|
4321
|
+
onClose(peer) {
|
|
4322
|
+
const found = this.#find(peer);
|
|
4323
|
+
if (!found) return;
|
|
4324
|
+
const [sid, slot, reg] = found;
|
|
4325
|
+
if (reg.pending) {
|
|
4326
|
+
const job = reg.pending.job;
|
|
4327
|
+
clearTimeout(reg.pending.timer);
|
|
4328
|
+
reg.pending = void 0;
|
|
4329
|
+
this.#d.log(`\u6307\u4EE4 ${job.cmd.instructionId} \u4EA4\u7ED9\u7B49\u5F85\u8005\u540E\u6CA1\u6536\u5230\u786E\u8BA4\uFF0C\u6309\u5931\u8D25\u5904\u7406`);
|
|
4330
|
+
this.#d.report(job.cmd, sid, "failed", "window_failed");
|
|
4331
|
+
}
|
|
4332
|
+
slot.reg = void 0;
|
|
4333
|
+
this.#settle(sid, slot);
|
|
4334
|
+
}
|
|
4335
|
+
/** 每秒一次:过期、窗口关闭、打断、等结果超时、忙转闲 */
|
|
4336
|
+
tick() {
|
|
4337
|
+
const now = this.#d.now();
|
|
4338
|
+
for (const [sid, slot] of [...this.#slots]) {
|
|
4339
|
+
if (slot.agentPid && !this.#d.alive(slot.agentPid)) {
|
|
4340
|
+
if (slot.reg) this.#evict(slot, sid);
|
|
4341
|
+
slot.flight = void 0;
|
|
4342
|
+
slot.agentPid = void 0;
|
|
4343
|
+
this.#drain(slot, "\u6392\u961F\u65F6\u7A97\u53E3\u5173\u4E86");
|
|
4344
|
+
}
|
|
4345
|
+
const f = slot.flight;
|
|
4346
|
+
if (f) {
|
|
4347
|
+
const t = slot.transcript ? this.#d.analyze(slot.transcript) : null;
|
|
4348
|
+
if (t && t.id === f.job.cmd.instructionId && t.interrupted) {
|
|
4349
|
+
this.#interrupted(sid, slot, f, "Esc");
|
|
4350
|
+
} else {
|
|
4351
|
+
const st = this.#d.readState(sid);
|
|
4352
|
+
if (st?.state === "ask_approval") f.paused += now - f.lastTick;
|
|
4353
|
+
f.lastTick = now;
|
|
4354
|
+
if (now - f.firedAt - f.paused > this.#d.flightLimitMs) {
|
|
4355
|
+
this.#d.log(`\u6307\u4EE4 ${f.job.cmd.instructionId} \u5524\u9192\u540E\u8FDF\u8FDF\u6CA1\u7B49\u5230\u8FD9\u4E00\u8F6E\u7ED3\u675F\uFF0C\u4E0D\u518D\u7B49\u7ED3\u679C`);
|
|
4356
|
+
slot.flight = void 0;
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
this.#settle(sid, slot);
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
logError(msg) {
|
|
4364
|
+
this.#d.log(msg);
|
|
4365
|
+
}
|
|
4366
|
+
/** 关掉全部连接(daemon 退出) */
|
|
4367
|
+
closeAll() {
|
|
4368
|
+
for (const slot of this.#slots.values()) {
|
|
4369
|
+
if (slot.reg?.pending) clearTimeout(slot.reg.pending.timer);
|
|
4370
|
+
slot.reg?.peer.close();
|
|
4371
|
+
}
|
|
4372
|
+
this.#slots.clear();
|
|
4373
|
+
}
|
|
4374
|
+
#slot(sid) {
|
|
4375
|
+
let s = this.#slots.get(sid);
|
|
4376
|
+
if (!s) {
|
|
4377
|
+
s = { queue: [], lastStopAt: 0, marked: false };
|
|
4378
|
+
this.#slots.set(sid, s);
|
|
4379
|
+
}
|
|
4380
|
+
return s;
|
|
4381
|
+
}
|
|
4382
|
+
#find(peer) {
|
|
4383
|
+
for (const [sid, s] of this.#slots) if (s.reg && s.reg.peer === peer) return [sid, s, s.reg];
|
|
4384
|
+
return null;
|
|
4385
|
+
}
|
|
4386
|
+
#evict(slot, sid) {
|
|
4387
|
+
const r = slot.reg;
|
|
4388
|
+
if (!r) return;
|
|
4389
|
+
slot.reg = void 0;
|
|
4390
|
+
if (r.pending) {
|
|
4391
|
+
clearTimeout(r.pending.timer);
|
|
4392
|
+
this.#d.log(`\u6307\u4EE4 ${r.pending.job.cmd.instructionId} \u4EA4\u4ED8\u4E2D\u7B49\u5F85\u8005\u88AB\u8BF7\u8D70\uFF0C\u6309\u5931\u8D25\u5904\u7406`);
|
|
4393
|
+
this.#d.report(r.pending.job.cmd, sid ?? r.pending.job.sessionId, "failed", "window_failed");
|
|
4394
|
+
r.pending = void 0;
|
|
4395
|
+
}
|
|
4396
|
+
r.peer.send({ t: "bye" });
|
|
4397
|
+
r.peer.close();
|
|
4398
|
+
}
|
|
4399
|
+
/** 没碰过窗口的排队指令按原顺序交回后台 */
|
|
4400
|
+
#drain(slot, why) {
|
|
4401
|
+
for (const j of slot.queue.splice(0)) {
|
|
4402
|
+
this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} ${why}\uFF0C\u6539\u8D70\u540E\u53F0`);
|
|
4403
|
+
this.#d.fallback(j.cmd);
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4406
|
+
#busy(sid, slot) {
|
|
4407
|
+
if (slot.flight || slot.reg?.pending) return true;
|
|
4408
|
+
const st = this.#d.readState(sid);
|
|
4409
|
+
if (!st || !BUSY_STATES.has(st.state) || st.updatedAt <= slot.lastStopAt) return false;
|
|
4410
|
+
if (slot.transcript && this.#d.analyze(slot.transcript)?.interrupted) return false;
|
|
4411
|
+
return this.#d.now() - st.updatedAt <= this.#d.watchStaleMs;
|
|
4412
|
+
}
|
|
4413
|
+
#expired(job, now) {
|
|
4414
|
+
return job.cmd.expireAt > 0 ? isExpired(job.cmd, now) : now - job.queuedAt > this.#d.maxQueueMs;
|
|
4415
|
+
}
|
|
4416
|
+
/** 排队 / 发出 / 过期 */
|
|
4417
|
+
#pump(sid, slot) {
|
|
4418
|
+
const now = this.#d.now();
|
|
4419
|
+
for (const j of [...slot.queue]) {
|
|
4420
|
+
if (!this.#expired(j, now)) continue;
|
|
4421
|
+
slot.queue.splice(slot.queue.indexOf(j), 1);
|
|
4422
|
+
this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} \u6392\u961F\u5230\u8FC7\u671F\u4E5F\u6CA1\u7B49\u5230\u7A97\u53E3\u7A7A\u95F2`);
|
|
4423
|
+
this.#d.report(j.cmd, sid, "rejected", "expired");
|
|
4424
|
+
}
|
|
4425
|
+
if (slot.queue.length === 0) return;
|
|
4426
|
+
if (!slot.reg || this.#busy(sid, slot)) {
|
|
4427
|
+
for (const j of slot.queue) {
|
|
4428
|
+
if (j.queuedSent) continue;
|
|
4429
|
+
j.queuedSent = true;
|
|
4430
|
+
this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} ${slot.reg ? "\u7A97\u53E3\u6B63\u5FD9" : "\u6682\u65E0\u7B49\u5F85\u8005"}\uFF0C\u6392\u961F`);
|
|
4431
|
+
this.#d.report(j.cmd, sid, "queued");
|
|
4432
|
+
}
|
|
4433
|
+
return;
|
|
4434
|
+
}
|
|
4435
|
+
const job = slot.queue.shift();
|
|
4436
|
+
const reg = slot.reg;
|
|
4437
|
+
reg.pending = {
|
|
4438
|
+
job,
|
|
4439
|
+
timer: setTimeout(() => {
|
|
4440
|
+
if (reg.pending?.job === job) reg.peer.close();
|
|
4441
|
+
}, this.#d.ackTimeoutMs)
|
|
4442
|
+
};
|
|
4443
|
+
reg.pending.timer.unref?.();
|
|
4444
|
+
reg.peer.send({ t: "fire", id: job.cmd.instructionId, text: job.cmd.text });
|
|
4445
|
+
}
|
|
4446
|
+
/** 等待者没了、又没有在跑的唤醒轮:排队的交回后台;然后排队 / 发出;同步标记;清空闲槽 */
|
|
4447
|
+
#settle(sid, slot) {
|
|
4448
|
+
if (!slot.reg && !slot.flight && slot.queue.length > 0) this.#drain(slot, "\u7B49\u5F85\u8005\u6CA1\u4E86");
|
|
4449
|
+
this.#pump(sid, slot);
|
|
4450
|
+
const on = !!slot.reg || !!slot.flight;
|
|
4451
|
+
if (on !== slot.marked) {
|
|
4452
|
+
slot.marked = on;
|
|
4453
|
+
this.#d.mark(sid, slot.agentPid, on);
|
|
4454
|
+
}
|
|
4455
|
+
if (!on && slot.queue.length === 0 && !slot.reg) this.#slots.delete(sid);
|
|
4456
|
+
}
|
|
4457
|
+
};
|
|
4458
|
+
function parseTail(v) {
|
|
4459
|
+
if (!v || typeof v !== "object") return void 0;
|
|
4460
|
+
const t = v;
|
|
4461
|
+
if (typeof t.id !== "string" || t.id !== "" && !isValidId(t.id)) return void 0;
|
|
4462
|
+
return { id: t.id, answered: t.answered === true, humanAfter: t.humanAfter === true, interrupted: t.interrupted === true };
|
|
4463
|
+
}
|
|
4464
|
+
function parseHello(m) {
|
|
4465
|
+
if (m.t !== "hello" || typeof m.sessionId !== "string") return null;
|
|
4466
|
+
const num = (v) => typeof v === "number" && Number.isInteger(v) && v > 0 ? v : void 0;
|
|
4467
|
+
return {
|
|
4468
|
+
sessionId: m.sessionId,
|
|
4469
|
+
agentPid: num(m.agentPid),
|
|
4470
|
+
waiterPid: num(m.waiterPid),
|
|
4471
|
+
event: typeof m.event === "string" ? m.event : void 0,
|
|
4472
|
+
last: typeof m.last === "string" ? m.last : void 0,
|
|
4473
|
+
tail: parseTail(m.tail),
|
|
4474
|
+
transcript: typeof m.transcript === "string" && m.transcript.endsWith(".jsonl") ? m.transcript : void 0,
|
|
4475
|
+
stopAt: num(m.stopAt),
|
|
4476
|
+
re: m.re === true
|
|
4477
|
+
};
|
|
4478
|
+
}
|
|
4479
|
+
var NONCE_RE = /^[0-9a-f]{32}$/;
|
|
4480
|
+
var AUTH_TIMEOUT_MS = 5e3;
|
|
4481
|
+
function startWakeServer(path, hub, secret, platform = process.platform) {
|
|
4482
|
+
if (platform !== "win32") {
|
|
4483
|
+
if (!ensureChannelDir(path)) return Promise.resolve(null);
|
|
4484
|
+
try {
|
|
4485
|
+
rmSync11(path, { force: true });
|
|
4486
|
+
} catch {
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
return new Promise((resolve5) => {
|
|
4490
|
+
const server = createServer3((sock) => {
|
|
4491
|
+
sock.on("error", () => {
|
|
4492
|
+
});
|
|
4493
|
+
let phase = "hello";
|
|
4494
|
+
let nonceW = "";
|
|
4495
|
+
let nonceD = "";
|
|
4496
|
+
let regSid = "";
|
|
4497
|
+
const authTimer = setTimeout(() => {
|
|
4498
|
+
if (phase !== "ready") sock.destroy();
|
|
4499
|
+
}, AUTH_TIMEOUT_MS);
|
|
4500
|
+
authTimer.unref();
|
|
4501
|
+
const raw = (msg) => {
|
|
4502
|
+
if (sock.destroyed || !sock.writable) return;
|
|
4503
|
+
try {
|
|
4504
|
+
sock.write(JSON.stringify(msg) + "\n");
|
|
4505
|
+
} catch {
|
|
4506
|
+
}
|
|
4507
|
+
};
|
|
4508
|
+
const peer = {
|
|
4509
|
+
send(msg) {
|
|
4510
|
+
if (msg.t === "fire") raw({ ...msg, sid: regSid, mac: wakeMac(secret, nonceW, regSid, String(msg.id), String(msg.text)) });
|
|
4511
|
+
else raw(msg);
|
|
4512
|
+
},
|
|
4513
|
+
close() {
|
|
4514
|
+
sock.end();
|
|
4515
|
+
setTimeout(() => sock.destroy(), 200).unref();
|
|
4516
|
+
}
|
|
4517
|
+
};
|
|
4518
|
+
sock.on("close", () => {
|
|
4519
|
+
clearTimeout(authTimer);
|
|
4520
|
+
if (phase === "ready") {
|
|
4521
|
+
try {
|
|
4522
|
+
hub.onClose(peer);
|
|
4523
|
+
} catch (e) {
|
|
4524
|
+
hub.logError(`\u7B49\u5F85\u8005\u65AD\u5F00\u65F6\u5904\u7406\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
});
|
|
4528
|
+
const onLine = (line) => {
|
|
4529
|
+
const m = JSON.parse(line);
|
|
4530
|
+
if (!m || typeof m !== "object") throw new Error("bad frame");
|
|
4531
|
+
if (phase === "hello") {
|
|
4532
|
+
if (m.t !== "hello" || typeof m.nonceW !== "string" || !NONCE_RE.test(m.nonceW)) {
|
|
4533
|
+
sock.destroy();
|
|
4534
|
+
return;
|
|
4535
|
+
}
|
|
4536
|
+
nonceW = m.nonceW;
|
|
4537
|
+
nonceD = randomBytes6(16).toString("hex");
|
|
4538
|
+
phase = "auth2";
|
|
4539
|
+
raw({ t: "auth", nonceD, mac: wakeMac(secret, "d", nonceW, nonceD) });
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4542
|
+
if (phase === "auth2") {
|
|
4543
|
+
const reg = m.reg && typeof m.reg === "object" ? m.reg : null;
|
|
4544
|
+
const hello = reg ? parseHello({ ...reg, t: "hello" }) : null;
|
|
4545
|
+
if (m.t !== "auth2" || !hello || !macEqual(m.mac, wakeMac(secret, "w", nonceD, canonicalJson(reg)))) {
|
|
4546
|
+
sock.destroy();
|
|
4547
|
+
return;
|
|
4548
|
+
}
|
|
4549
|
+
regSid = hello.sessionId;
|
|
4550
|
+
phase = "ready";
|
|
4551
|
+
clearTimeout(authTimer);
|
|
4552
|
+
hub.onHello(peer, hello);
|
|
4553
|
+
return;
|
|
4554
|
+
}
|
|
4555
|
+
if (m.t !== "fired" && m.t !== "refuse" || typeof m.id !== "string" || !macEqual(m.mac, wakeMac(secret, "u", nonceD, m.t, m.id))) {
|
|
4556
|
+
sock.destroy();
|
|
4557
|
+
return;
|
|
4558
|
+
}
|
|
4559
|
+
hub.onMessage(peer, m);
|
|
4560
|
+
};
|
|
4561
|
+
lines(sock, (line) => {
|
|
4562
|
+
try {
|
|
4563
|
+
onLine(line);
|
|
4564
|
+
} catch {
|
|
4565
|
+
sock.destroy();
|
|
4566
|
+
}
|
|
4567
|
+
});
|
|
4568
|
+
});
|
|
4569
|
+
const oldMask = platform === "win32" ? null : process.umask(127);
|
|
4570
|
+
const restore = () => {
|
|
4571
|
+
if (oldMask !== null) process.umask(oldMask);
|
|
4572
|
+
};
|
|
4573
|
+
server.once("error", () => {
|
|
4574
|
+
restore();
|
|
4575
|
+
resolve5(null);
|
|
4576
|
+
});
|
|
4577
|
+
server.listen(path, () => {
|
|
4578
|
+
restore();
|
|
4579
|
+
server.on("error", () => {
|
|
4580
|
+
});
|
|
4581
|
+
resolve5(server);
|
|
4582
|
+
});
|
|
4583
|
+
});
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
// src/daemon/index.ts
|
|
4587
|
+
var IDLE_TIMEOUT_MS = 150 * 1e3;
|
|
4588
|
+
var POLICY_VIOLATION = 1008;
|
|
4589
|
+
var PING_FRAME_SERVICE = "ping";
|
|
4590
|
+
var PONG_FRAME = JSON.stringify({ service: "pong", version: "1.0" });
|
|
4591
|
+
var CODEX_REMOTE_REFRESH_MS = 30 * 1e3;
|
|
4592
|
+
var CLAIM_WAIT_MS = 15 * 1e3;
|
|
4593
|
+
var WINDOW_READY_MS = 30 * 1e3;
|
|
4594
|
+
var CODEX_WATCH_MS = 5 * 1e3;
|
|
4595
|
+
function defaultCodexAppServers() {
|
|
4596
|
+
const cfg = loadConfig();
|
|
4597
|
+
return cfg.enabledTools.includes("codex") ? discoverAppServers(cfg.codexAppServer) : [];
|
|
4598
|
+
}
|
|
4599
|
+
function opencodeReady() {
|
|
4600
|
+
return isOcPluginInstalled(opencodePluginPath()) && existsSync10(ocPluginModule(installedHookPath()));
|
|
4601
|
+
}
|
|
4602
|
+
function orphanResumeText(summary2) {
|
|
4603
|
+
return `\uFF08\u4F60\u5728\u8BBE\u5907\u4E0A\u5DF2\u6279\u51C6\uFF1A${summary2}\uFF09\u8BF7\u7EE7\u7EED\u521A\u624D\u88AB\u4E2D\u65AD\u7684\u64CD\u4F5C\u3002`;
|
|
4604
|
+
}
|
|
3573
4605
|
var ALIVE_REPORT_MS = 60 * 1e3;
|
|
3574
4606
|
var Daemon = class {
|
|
3575
4607
|
#queue = new SessionQueue();
|
|
@@ -3587,6 +4619,15 @@ var Daemon = class {
|
|
|
3587
4619
|
#windowScan = null;
|
|
3588
4620
|
#aliveReport = null;
|
|
3589
4621
|
#codexTimer = null;
|
|
4622
|
+
#wake;
|
|
4623
|
+
/**
|
|
4624
|
+
* 会话号 → 这个会话正在进行的开窗流程(开窗、等认领、交首句;降级后台时一直到后台那次跑完)。
|
|
4625
|
+
* 🔴 同一会话不能被两个进程同时续跑(实测会静默丢一整轮):后到的指令等它结束再路由
|
|
4626
|
+
*/
|
|
4627
|
+
#opening = /* @__PURE__ */ new Map();
|
|
4628
|
+
#wakeServer = null;
|
|
4629
|
+
#wakeTimer = null;
|
|
4630
|
+
#wakeReport = null;
|
|
3590
4631
|
/** url → 连着它的 CodexWatch。插入顺序 = 发现顺序(DeskBuddy 管的在前) */
|
|
3591
4632
|
#codex = /* @__PURE__ */ new Map();
|
|
3592
4633
|
#codexStarted = false;
|
|
@@ -3607,8 +4648,35 @@ var Daemon = class {
|
|
|
3607
4648
|
codexRoute: deps.codexRoute ?? ((tid) => this.#codexRouteFor(tid)),
|
|
3608
4649
|
report: deps.report ?? ((p) => api.commandResult(p)),
|
|
3609
4650
|
windowFor: deps.windowFor ?? windowForSession,
|
|
3610
|
-
sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), req2, onReply))
|
|
4651
|
+
sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), windowSecret(id), req2, onReply)),
|
|
4652
|
+
gui: deps.gui ?? (() => guiAvailable(defaultGuiDeps())),
|
|
4653
|
+
openWindow: deps.openWindow ?? ((runId) => this.#openTerminal(runId)),
|
|
4654
|
+
endedCwd: deps.endedCwd ?? ((sid, tool, stateCwd) => resolveEndedCwd(sid, tool, {
|
|
4655
|
+
readEnded: (s) => readEnded(s) ?? (stateCwd ? { sessionId: s, tool, cwd: stateCwd, interactive: false, endedAt: 0 } : null),
|
|
4656
|
+
transcriptCwd: (s) => claudeTranscriptCwd(s),
|
|
4657
|
+
isDir: isDirSync
|
|
4658
|
+
})),
|
|
4659
|
+
claimWaitMs: deps.claimWaitMs ?? CLAIM_WAIT_MS,
|
|
4660
|
+
windowReadyMs: deps.windowReadyMs ?? WINDOW_READY_MS,
|
|
4661
|
+
wakePath: deps.wakePath === void 0 ? wakeChannelPath() : deps.wakePath,
|
|
4662
|
+
onWindowDone: deps.onWindowDone ?? (() => {
|
|
4663
|
+
})
|
|
3611
4664
|
};
|
|
4665
|
+
this.#wake = new WakeHub({
|
|
4666
|
+
now: this.#deps.now,
|
|
4667
|
+
readState,
|
|
4668
|
+
alive: (pid) => isAlive(pid),
|
|
4669
|
+
report: (cmd, sid, status, reason) => this.#report(cmd, sid, status, "window", reason ?? ""),
|
|
4670
|
+
fallback: (cmd) => this.#dispatch(cmd),
|
|
4671
|
+
done: (cmd, sid, text) => this.#deps.onWindowDone({ instructionId: cmd.instructionId, sessionId: sid, tool: cmd.tool, text }),
|
|
4672
|
+
mark: (sid, pid, on) => {
|
|
4673
|
+
if (on) writeWakeMark(sid, pid, this.#deps.now());
|
|
4674
|
+
else removeWakeMark(sid);
|
|
4675
|
+
log(`\u4F1A\u8BDD ${sid} ${on ? "\u53EF\u4EE5" : "\u4E0D\u518D\u80FD"}\u88AB\u8BBE\u5907\u6307\u4EE4\u5524\u9192`);
|
|
4676
|
+
this.#scheduleWakeReport();
|
|
4677
|
+
},
|
|
4678
|
+
log
|
|
4679
|
+
});
|
|
3612
4680
|
this.#store.prune(this.#deps.now());
|
|
3613
4681
|
this.#coord = new ApprovalCoordinator({
|
|
3614
4682
|
store: this.#store,
|
|
@@ -3632,8 +4700,55 @@ var Daemon = class {
|
|
|
3632
4700
|
this.#codexTick();
|
|
3633
4701
|
this.#codexTimer = setInterval(() => this.#codexTick(), CODEX_WATCH_MS);
|
|
3634
4702
|
this.#codexTimer.unref();
|
|
4703
|
+
await this.startWake();
|
|
3635
4704
|
return port;
|
|
3636
4705
|
}
|
|
4706
|
+
/**
|
|
4707
|
+
* 起窗口唤醒通道(Claude Code asyncRewake 等待者连这里,见 hook/wake.cjs 与 ./wake.ts)。
|
|
4708
|
+
* 先清上次残留的标记:还活着的等待者几秒内会重连重新登记
|
|
4709
|
+
*/
|
|
4710
|
+
async startWake() {
|
|
4711
|
+
const path = this.#deps.wakePath;
|
|
4712
|
+
if (!path) return;
|
|
4713
|
+
clearWakeMarks();
|
|
4714
|
+
const secret = randomBytes7(32);
|
|
4715
|
+
let warn = "";
|
|
4716
|
+
try {
|
|
4717
|
+
warn = writeWakeSecret(secret.toString("hex"));
|
|
4718
|
+
} catch (e) {
|
|
4719
|
+
log(`\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u5199\u4E0D\u8FDB\u53BB\uFF0C\u4E0D\u8D77\u5524\u9192\u901A\u9053\uFF1A${e.message}`);
|
|
4720
|
+
return;
|
|
4721
|
+
}
|
|
4722
|
+
if (warn) log(`\u26A0\uFE0F \u5B89\u5168\u544A\u8B66\uFF1A${warn}`);
|
|
4723
|
+
this.#wakeServer = await startWakeServer(path, this.#wake, secret);
|
|
4724
|
+
if (this.#wakeServer) log("\u7A97\u53E3\u5524\u9192\u901A\u9053\u5DF2\u5C31\u7EEA");
|
|
4725
|
+
else if (process.platform === "win32")
|
|
4726
|
+
log("\u26A0\uFE0F \u5B89\u5168\u544A\u8B66\uFF1A\u7A97\u53E3\u5524\u9192\u7BA1\u9053\u76D1\u542C\u5931\u8D25\uFF0C\u53EF\u80FD\u5DF2\u88AB\u5176\u4ED6\u8FDB\u7A0B\u62A2\u6CE8\uFF1B\u5DF2\u505C\u7528\u7A97\u53E3\u5524\u9192\uFF08\u7B49\u5F85\u8005\u8BA4\u8BC1\u4E0D\u8FC7\u4E0D\u4F1A\u6267\u884C\u4EFB\u4F55\u4E1C\u897F\uFF09");
|
|
4727
|
+
else log("\u7A97\u53E3\u5524\u9192\u901A\u9053\u6CA1\u8D77\u6765\uFF08\u4E0D\u5F71\u54CD\u5176\u5B83\u529F\u80FD\uFF09");
|
|
4728
|
+
this.#wakeTimer = setInterval(() => this.#wake.tick(), 1e3);
|
|
4729
|
+
this.#wakeTimer.unref();
|
|
4730
|
+
}
|
|
4731
|
+
/** 唤醒能力变了:300ms 内合并,报一次 Claude Code 的快照(remoteWindow 跟着变) */
|
|
4732
|
+
#scheduleWakeReport() {
|
|
4733
|
+
if (this.#wakeReport) return;
|
|
4734
|
+
this.#wakeReport = setTimeout(() => {
|
|
4735
|
+
this.#wakeReport = null;
|
|
4736
|
+
void this.#reportTool("claude_code");
|
|
4737
|
+
}, 300);
|
|
4738
|
+
this.#wakeReport.unref();
|
|
4739
|
+
}
|
|
4740
|
+
async #reportTool(tool) {
|
|
4741
|
+
const cfg = loadConfig();
|
|
4742
|
+
if (!cfg.clientId || !cfg.boundAt || !cfg.enabledTools.includes(tool)) return;
|
|
4743
|
+
try {
|
|
4744
|
+
await postReport(buildSnapshot(withWake(readAliveStates(tool)), tool, cfg, this.#deps.now()));
|
|
4745
|
+
} catch (e) {
|
|
4746
|
+
log(`\u4E0A\u62A5 ${tool} \u4F1A\u8BDD\u5931\u8D25\uFF1A${e.message}`);
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
get wake() {
|
|
4750
|
+
return this.#wake;
|
|
4751
|
+
}
|
|
3637
4752
|
/** 找 app-server:新出现的连上,不在了且已断开的丢掉;连着的各自 tick */
|
|
3638
4753
|
#codexTick() {
|
|
3639
4754
|
let urls = [];
|
|
@@ -3728,7 +4843,9 @@ var Daemon = class {
|
|
|
3728
4843
|
const cfg = loadConfig();
|
|
3729
4844
|
if (!cfg.clientId || !cfg.boundAt) return;
|
|
3730
4845
|
for (const tool of cfg.enabledTools) {
|
|
3731
|
-
await postReport(
|
|
4846
|
+
await postReport(
|
|
4847
|
+
buildSnapshot(withWake(readAliveStates(tool)), tool, cfg, this.#deps.now(), tool === "codex" ? this.#codexLoaded() : null)
|
|
4848
|
+
);
|
|
3732
4849
|
}
|
|
3733
4850
|
if (++this.#dirsTick % 10 === 0) await this.#reportDirs();
|
|
3734
4851
|
}
|
|
@@ -3741,6 +4858,10 @@ var Daemon = class {
|
|
|
3741
4858
|
if (this.#windowScan) clearInterval(this.#windowScan);
|
|
3742
4859
|
if (this.#aliveReport) clearInterval(this.#aliveReport);
|
|
3743
4860
|
if (this.#codexTimer) clearInterval(this.#codexTimer);
|
|
4861
|
+
if (this.#wakeTimer) clearInterval(this.#wakeTimer);
|
|
4862
|
+
if (this.#wakeReport) clearTimeout(this.#wakeReport);
|
|
4863
|
+
this.#wake.closeAll();
|
|
4864
|
+
this.#wakeServer?.close();
|
|
3744
4865
|
if (this.#codexReportTimer) clearTimeout(this.#codexReportTimer);
|
|
3745
4866
|
for (const w of this.#codex.values()) w.stop();
|
|
3746
4867
|
this.#codex.clear();
|
|
@@ -3794,7 +4915,11 @@ var Daemon = class {
|
|
|
3794
4915
|
});
|
|
3795
4916
|
ws.on("message", (raw) => {
|
|
3796
4917
|
this.#armIdle();
|
|
3797
|
-
|
|
4918
|
+
try {
|
|
4919
|
+
this.#onMessage(String(raw));
|
|
4920
|
+
} catch (e) {
|
|
4921
|
+
log(`\u5904\u7406\u4E0B\u884C\u5E27\u51FA\u9519\uFF1A${e.stack ?? e.message}`);
|
|
4922
|
+
}
|
|
3798
4923
|
});
|
|
3799
4924
|
ws.on("error", (e) => log(`\u8FDE\u63A5\u51FA\u9519: ${e.message}`));
|
|
3800
4925
|
ws.on("close", (code, reason) => {
|
|
@@ -3848,6 +4973,22 @@ var Daemon = class {
|
|
|
3848
4973
|
return;
|
|
3849
4974
|
}
|
|
3850
4975
|
this.#seen.save();
|
|
4976
|
+
this.#route(cmd);
|
|
4977
|
+
}
|
|
4978
|
+
/**
|
|
4979
|
+
* 指令分派(去重之后)。同一会话的开窗流程还没走完时,后到的指令等它结束再从头路由一遍
|
|
4980
|
+
* (届时窗口已登记 → 走窗口写入;降级后台了 → 按原来的按会话串行排队)
|
|
4981
|
+
*/
|
|
4982
|
+
#route(cmd) {
|
|
4983
|
+
if (cmd.tool === "claude_code" && cmd.sessionId) {
|
|
4984
|
+
const sid = resolveAlias(cmd.sessionId);
|
|
4985
|
+
const pending = this.#opening.get(sid);
|
|
4986
|
+
if (pending) {
|
|
4987
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u7B49\u540C\u4E00\u4F1A\u8BDD\u7684\u5F00\u7A97\u6D41\u7A0B\u7ED3\u675F\u540E\u518D\u5206\u6D3E`);
|
|
4988
|
+
void pending.then(() => this.#route(cmd));
|
|
4989
|
+
return;
|
|
4990
|
+
}
|
|
4991
|
+
}
|
|
3851
4992
|
if (cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
|
|
3852
4993
|
const sid = resolveAlias(cmd.sessionId);
|
|
3853
4994
|
let win = null;
|
|
@@ -3878,8 +5019,208 @@ var Daemon = class {
|
|
|
3878
5019
|
}
|
|
3879
5020
|
}
|
|
3880
5021
|
}
|
|
5022
|
+
this.#afterPty(cmd);
|
|
5023
|
+
}
|
|
5024
|
+
/**
|
|
5025
|
+
* pty 窗口接管之外的两条路:窗口里有登记中的唤醒等待者 → 交给它(忙时排队);否则原来的后台路径。
|
|
5026
|
+
* 🔴 一条指令只走一条路:交给等待者之后的任何结局都由 WakeHub 负责,不再回到这里
|
|
5027
|
+
*/
|
|
5028
|
+
#afterPty(cmd) {
|
|
5029
|
+
if (cmd.tool === "claude_code" && cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
|
|
5030
|
+
const sid = resolveAlias(cmd.sessionId);
|
|
5031
|
+
if (this.#wake.submit(cmd, sid)) return;
|
|
5032
|
+
}
|
|
5033
|
+
if (cmd.tool === "claude_code" && !isExpired(cmd, this.#deps.now())) {
|
|
5034
|
+
void this.#openOrDispatch(cmd).catch((e) => {
|
|
5035
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u5F00\u7A97\u6D41\u7A0B\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
|
|
5036
|
+
this.#report(cmd, cmd.sessionId, "failed", "", "window_failed");
|
|
5037
|
+
});
|
|
5038
|
+
return;
|
|
5039
|
+
}
|
|
5040
|
+
if (cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
|
|
5041
|
+
const sid = resolveAlias(cmd.sessionId);
|
|
5042
|
+
if (!readState(sid) && readEnded(sid)?.tool === cmd.tool) {
|
|
5043
|
+
const r = this.#deps.endedCwd(sid, cmd.tool, "");
|
|
5044
|
+
if (!r.ok) {
|
|
5045
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\u4F46${r.reason === "no_cwd" ? "\u53D6\u4E0D\u5230\u53EF\u9760\u7684\u5DE5\u4F5C\u76EE\u5F55" : "\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5728"}\uFF0C\u4E0D\u6267\u884C`);
|
|
5046
|
+
this.#report(cmd, sid, "rejected", "", r.reason);
|
|
5047
|
+
return;
|
|
5048
|
+
}
|
|
5049
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\uFF0C\u540E\u53F0\u7EED\u8DD1`);
|
|
5050
|
+
void this.#headless(cmd, sid, r.cwd);
|
|
5051
|
+
return;
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
3881
5054
|
this.#dispatch(cmd);
|
|
3882
5055
|
}
|
|
5056
|
+
/**
|
|
5057
|
+
* 新建任务、会话已结束 → 开窗;会话还活着(后台 -p 在跑、或在用户自己开的窗口里)→ 原来的路径(排队 / window_open)
|
|
5058
|
+
*/
|
|
5059
|
+
async #openOrDispatch(cmd) {
|
|
5060
|
+
if (tooLongToInject(cmd.text)) {
|
|
5061
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u592A\u957F\uFF08${cmd.text.length} \u5B57\uFF09\uFF0C\u4E0D\u6267\u884C`);
|
|
5062
|
+
this.#report(cmd, cmd.sessionId ? resolveAlias(cmd.sessionId) : "", "rejected", "", TEXT_TOO_LONG);
|
|
5063
|
+
return;
|
|
5064
|
+
}
|
|
5065
|
+
if (!cmd.sessionId) {
|
|
5066
|
+
let cwd = "";
|
|
5067
|
+
if (cmd.dirRef) {
|
|
5068
|
+
cwd = dirOf(cmd.dirRef);
|
|
5069
|
+
if (!cwd) {
|
|
5070
|
+
this.#report(cmd, "", "rejected", "", "dir_gone");
|
|
5071
|
+
return;
|
|
5072
|
+
}
|
|
5073
|
+
} else {
|
|
5074
|
+
cwd = loadConfig().remoteCwd || "";
|
|
5075
|
+
if (!cwd) {
|
|
5076
|
+
this.#report(cmd, "", "rejected", "", "no_remote_cwd");
|
|
5077
|
+
return;
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
await this.#openWindowFor(cmd, "", cwd);
|
|
5081
|
+
return;
|
|
5082
|
+
}
|
|
5083
|
+
const sid = resolveAlias(cmd.sessionId);
|
|
5084
|
+
for (let p = this.#opening.get(sid); p; p = this.#opening.get(sid)) await p;
|
|
5085
|
+
let release2;
|
|
5086
|
+
this.#opening.set(sid, new Promise((r) => release2 = r));
|
|
5087
|
+
try {
|
|
5088
|
+
if (this.#queue.busy(sid)) await this.#queue.enqueue(sid, async () => {
|
|
5089
|
+
});
|
|
5090
|
+
const st = readState(sid);
|
|
5091
|
+
if (st && (!st.pid || isAlive(st.pid, st.pidName))) {
|
|
5092
|
+
this.#dispatch(cmd);
|
|
5093
|
+
return;
|
|
5094
|
+
}
|
|
5095
|
+
if (!SESSION_UUID_RE.test(sid)) {
|
|
5096
|
+
this.#dispatch(cmd);
|
|
5097
|
+
return;
|
|
5098
|
+
}
|
|
5099
|
+
const r = this.#deps.endedCwd(sid, cmd.tool, st?.cwd ?? "");
|
|
5100
|
+
if (!r.ok) {
|
|
5101
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u5DF2\u7ED3\u675F\u4F46${r.reason === "no_cwd" ? "\u53D6\u4E0D\u5230\u53EF\u9760\u7684\u5DE5\u4F5C\u76EE\u5F55" : "\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5728"}\uFF0C\u4E0D\u6267\u884C`);
|
|
5102
|
+
this.#report(cmd, sid, "rejected", "", r.reason);
|
|
5103
|
+
return;
|
|
5104
|
+
}
|
|
5105
|
+
await this.#openWindowFor(cmd, sid, r.cwd);
|
|
5106
|
+
} finally {
|
|
5107
|
+
this.#opening.delete(sid);
|
|
5108
|
+
release2();
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
/**
|
|
5112
|
+
* 开窗 → 等认领 → 首句经通道交给窗口;任何一步起不来 → 后台 -p(回报 started/headless)。
|
|
5113
|
+
* 返回时:窗口已收下首句 / 已按失败回报 / 降级的后台那次已跑完
|
|
5114
|
+
*/
|
|
5115
|
+
async #openWindowFor(cmd, sid, cwd) {
|
|
5116
|
+
const headless = (why) => {
|
|
5117
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} ${why}\uFF0C\u6539\u540E\u53F0\u6267\u884C`);
|
|
5118
|
+
return this.#headless(cmd, sid, cwd);
|
|
5119
|
+
};
|
|
5120
|
+
let gui = false;
|
|
5121
|
+
try {
|
|
5122
|
+
gui = this.#deps.gui();
|
|
5123
|
+
} catch {
|
|
5124
|
+
gui = false;
|
|
5125
|
+
}
|
|
5126
|
+
if (!gui) return headless("\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\uFF08SSH / \u9501\u5B9A\u7684\u670D\u52A1\u4F1A\u8BDD / Linux\uFF09");
|
|
5127
|
+
const runId = randomBytes7(16).toString("hex");
|
|
5128
|
+
try {
|
|
5129
|
+
writeLaunch(runId, { tool: "claude_code", cwd, resume: sid, at: this.#deps.now() });
|
|
5130
|
+
} catch (e) {
|
|
5131
|
+
return headless(`\u5199\u4EA4\u63A5\u5355\u5931\u8D25\uFF1A${e.message}`);
|
|
5132
|
+
}
|
|
5133
|
+
try {
|
|
5134
|
+
await this.#deps.openWindow(runId);
|
|
5135
|
+
} catch (e) {
|
|
5136
|
+
if (claimLaunch(runId)) {
|
|
5137
|
+
removeCommandFile(deskbuddyHome(), runId);
|
|
5138
|
+
return headless(`\u5F00\u7A97\u5931\u8D25\uFF1A${e.message}`);
|
|
5139
|
+
}
|
|
5140
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u5F00\u7A97\u547D\u4EE4\u62A5\u9519\uFF08${e.message}\uFF09\uFF0C\u4F46\u7A97\u53E3\u5DF2\u8BA4\u9886\uFF0C\u6309\u7A97\u53E3\u7EE7\u7EED`);
|
|
5141
|
+
}
|
|
5142
|
+
const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
5143
|
+
const claimDeadline = this.#deps.now() + this.#deps.claimWaitMs;
|
|
5144
|
+
while (launchPending(runId) && this.#deps.now() < claimDeadline) await sleep2(300);
|
|
5145
|
+
removeCommandFile(deskbuddyHome(), runId);
|
|
5146
|
+
if (launchPending(runId) && claimLaunch(runId)) return headless(`${Math.round(this.#deps.claimWaitMs / 1e3)} \u79D2\u5185\u7A97\u53E3\u6CA1\u8D77\u6765`);
|
|
5147
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u7A97\u53E3\u5DF2\u8D77\u6765\uFF08${runId.slice(0, 8)}\uFF09\uFF0C\u7B49\u5B83\u5C31\u7EEA\u540E\u5199\u5165\u9996\u53E5`);
|
|
5148
|
+
const req2 = { instructionId: cmd.instructionId, sessionId: "", text: cmd.text, expireAt: cmd.expireAt };
|
|
5149
|
+
const fb = { p: null, final: false };
|
|
5150
|
+
const readyDeadline = this.#deps.now() + this.#deps.windowReadyMs;
|
|
5151
|
+
while (this.#deps.now() < readyDeadline) {
|
|
5152
|
+
let r = "fallback";
|
|
5153
|
+
try {
|
|
5154
|
+
r = await this.#deps.sendToWindow(runId, req2, (reply) => {
|
|
5155
|
+
if (reply.status === "ack") return;
|
|
5156
|
+
if (reply.status !== "queued") fb.final = true;
|
|
5157
|
+
if (reply.status === "not_here") {
|
|
5158
|
+
if (!fb.p) fb.p = headless("\u9996\u53E5\u5199\u5165\u524D\u7A97\u53E3\u5DF2\u5173\u95ED");
|
|
5159
|
+
return;
|
|
5160
|
+
}
|
|
5161
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u7A97\u53E3 ${reply.status}${reply.reason ? " " + reply.reason : ""}`);
|
|
5162
|
+
this.#report(cmd, sid, reply.status, "window", reply.reason ?? "");
|
|
5163
|
+
});
|
|
5164
|
+
} catch {
|
|
5165
|
+
r = "fallback";
|
|
5166
|
+
}
|
|
5167
|
+
if (r === "handled") {
|
|
5168
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u9996\u53E5\u5DF2\u4EA4\u7ED9\u65B0\u7A97\u53E3 len=${cmd.text.length}`);
|
|
5169
|
+
if (sid) {
|
|
5170
|
+
const left = cmd.expireAt > 0 ? Math.max(cmd.expireAt - this.#deps.now(), 0) : 10 * 60 * 1e3;
|
|
5171
|
+
const cap = this.#deps.now() + left + this.#deps.windowReadyMs;
|
|
5172
|
+
while (!fb.final && this.#deps.now() < cap) {
|
|
5173
|
+
let found = null;
|
|
5174
|
+
try {
|
|
5175
|
+
found = this.#deps.windowFor(sid);
|
|
5176
|
+
} catch {
|
|
5177
|
+
found = null;
|
|
5178
|
+
}
|
|
5179
|
+
if (found) break;
|
|
5180
|
+
await sleep2(200);
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
await sleep2(50);
|
|
5184
|
+
if (fb.p) await fb.p;
|
|
5185
|
+
return;
|
|
5186
|
+
}
|
|
5187
|
+
await sleep2(1e3);
|
|
5188
|
+
}
|
|
5189
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u65B0\u7A97\u53E3\u7684\u901A\u9053\u4E00\u76F4\u6CA1\u5C31\u7EEA`);
|
|
5190
|
+
this.#report(cmd, sid, "failed", "window", "window_failed");
|
|
5191
|
+
}
|
|
5192
|
+
/**
|
|
5193
|
+
* 后台 -p:新建任务走原来的路径;续跑已结束会话用解析出的 cwd,不再查状态文件(它已被删)。
|
|
5194
|
+
* 返回的 promise 在后台那次跑完时落定(开窗流程的会话锁要一直占到那时)
|
|
5195
|
+
*/
|
|
5196
|
+
#headless(cmd, sid, cwd) {
|
|
5197
|
+
if (isExpired(cmd, this.#deps.now())) {
|
|
5198
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u5DF2\u8FC7\u671F\uFF0C\u4E0D\u518D\u540E\u53F0\u6267\u884C`);
|
|
5199
|
+
this.#report(cmd, sid, "rejected", "", "expired");
|
|
5200
|
+
return Promise.resolve();
|
|
5201
|
+
}
|
|
5202
|
+
if (!sid) {
|
|
5203
|
+
this.#dispatch(cmd);
|
|
5204
|
+
return Promise.resolve();
|
|
5205
|
+
}
|
|
5206
|
+
return this.#queue.enqueue(
|
|
5207
|
+
sid,
|
|
5208
|
+
() => this.#execute(cmd, sid, cwd, cmd.tool, true),
|
|
5209
|
+
(e) => {
|
|
5210
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u65F6\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
|
|
5211
|
+
this.#report(cmd, sid, "failed", "", "exec_failed");
|
|
5212
|
+
}
|
|
5213
|
+
);
|
|
5214
|
+
}
|
|
5215
|
+
/** 真的弹窗口(测试环境绝不弹:vitest 下直接当开窗失败) */
|
|
5216
|
+
async #openTerminal(runId) {
|
|
5217
|
+
if (process.env.VITEST) throw new Error("\u6D4B\u8BD5\u73AF\u5883\u4E0D\u5F00\u7A97");
|
|
5218
|
+
const how = await openTerminal(
|
|
5219
|
+
{ node: this.#deps.nodePath, cli: defaultCliPath(), home: deskbuddyHome(), runId },
|
|
5220
|
+
defaultOpenerDeps(() => findWt())
|
|
5221
|
+
);
|
|
5222
|
+
log(`\u5F00\u7A97 ${runId.slice(0, 8)} \u65B9\u5F0F=${how}`);
|
|
5223
|
+
}
|
|
3883
5224
|
/**
|
|
3884
5225
|
* 经 app-server 往 Codex 会话里发一轮(切片 B)。回报语义与 Claude 窗口一致(mode=window):
|
|
3885
5226
|
* 排队 queued → 发出 started;排队中过期 rejected/expired。
|
|
@@ -3954,7 +5295,7 @@ var Daemon = class {
|
|
|
3954
5295
|
const fallback = () => {
|
|
3955
5296
|
if (fellBack) return;
|
|
3956
5297
|
fellBack = true;
|
|
3957
|
-
this.#
|
|
5298
|
+
this.#afterPty(cmd);
|
|
3958
5299
|
};
|
|
3959
5300
|
const req2 = { instructionId: cmd.instructionId, sessionId, text: cmd.text, expireAt: cmd.expireAt };
|
|
3960
5301
|
let r;
|
|
@@ -3998,10 +5339,17 @@ var Daemon = class {
|
|
|
3998
5339
|
return;
|
|
3999
5340
|
}
|
|
4000
5341
|
if (target.sessionId) this.#coord.cancelSession(target.sessionId);
|
|
4001
|
-
this.#queue.enqueue(
|
|
5342
|
+
this.#queue.enqueue(
|
|
5343
|
+
target.sessionId,
|
|
5344
|
+
() => this.#execute(cmd, target.sessionId, target.cwd, target.handleKey),
|
|
5345
|
+
(e) => {
|
|
5346
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u65F6\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
|
|
5347
|
+
this.#report(cmd, target.sessionId, "failed", "", "exec_failed");
|
|
5348
|
+
}
|
|
5349
|
+
);
|
|
4002
5350
|
}
|
|
4003
|
-
async #execute(cmd, sessionId, cwd, handleKey = cmd.tool) {
|
|
4004
|
-
if (sessionId) {
|
|
5351
|
+
async #execute(cmd, sessionId, cwd, handleKey = cmd.tool, ended = false) {
|
|
5352
|
+
if (sessionId && !ended) {
|
|
4005
5353
|
const fresh = readState(sessionId);
|
|
4006
5354
|
if (!fresh) {
|
|
4007
5355
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u653E\u5F03\uFF1A\u4F1A\u8BDD ${sessionId} \u5728\u6267\u884C\u524D\u5DF2\u6D88\u5931`);
|
|
@@ -4050,7 +5398,13 @@ var Daemon = class {
|
|
|
4050
5398
|
return;
|
|
4051
5399
|
}
|
|
4052
5400
|
const origin = this.#runsDone.get(r.runId) ?? Promise.resolve();
|
|
4053
|
-
void origin.then(
|
|
5401
|
+
void origin.then(
|
|
5402
|
+
() => this.#queue.enqueue(
|
|
5403
|
+
r.sessionId,
|
|
5404
|
+
() => this.#resume(r, kind),
|
|
5405
|
+
(e) => log(`\u7EED\u8DD1\u4F1A\u8BDD ${r.sessionId} \u51FA\u9519\uFF1A${e?.stack ?? String(e)}`)
|
|
5406
|
+
)
|
|
5407
|
+
);
|
|
4054
5408
|
}
|
|
4055
5409
|
async #resume(r, kind) {
|
|
4056
5410
|
if (kind === "deferred" && r.consumed) {
|
|
@@ -4119,7 +5473,7 @@ var Daemon = class {
|
|
|
4119
5473
|
}
|
|
4120
5474
|
};
|
|
4121
5475
|
function newRunId() {
|
|
4122
|
-
return
|
|
5476
|
+
return randomBytes7(12).toString("hex");
|
|
4123
5477
|
}
|
|
4124
5478
|
async function runDaemon(deps = {}) {
|
|
4125
5479
|
const d = new Daemon(deps);
|
|
@@ -4137,7 +5491,7 @@ async function runDaemonCommand(opts = {}) {
|
|
|
4137
5491
|
}
|
|
4138
5492
|
if (opts.foreground) process.env.DESKBUDDY_DAEMON_FOREGROUND = "1";
|
|
4139
5493
|
try {
|
|
4140
|
-
|
|
5494
|
+
writeFileSync17(pidFilePath(), String(process.pid), "utf8");
|
|
4141
5495
|
} catch {
|
|
4142
5496
|
}
|
|
4143
5497
|
let daemon;
|
|
@@ -4153,7 +5507,7 @@ async function runDaemonCommand(opts = {}) {
|
|
|
4153
5507
|
log(`\u6536\u5230 ${sig}\uFF0C\u9000\u51FA`);
|
|
4154
5508
|
daemon.stop();
|
|
4155
5509
|
try {
|
|
4156
|
-
|
|
5510
|
+
rmSync12(pidFilePath(), { force: true });
|
|
4157
5511
|
} catch {
|
|
4158
5512
|
}
|
|
4159
5513
|
process.exit(0);
|
|
@@ -4170,8 +5524,8 @@ async function runDaemonCommand(opts = {}) {
|
|
|
4170
5524
|
}
|
|
4171
5525
|
|
|
4172
5526
|
// src/core/registry.ts
|
|
4173
|
-
import { mkdirSync as
|
|
4174
|
-
import { join as
|
|
5527
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync16, writeFileSync as writeFileSync18 } from "fs";
|
|
5528
|
+
import { join as join21 } from "path";
|
|
4175
5529
|
var FALLBACK = {
|
|
4176
5530
|
version: "builtin",
|
|
4177
5531
|
tools: {
|
|
@@ -4249,11 +5603,11 @@ var FALLBACK = {
|
|
|
4249
5603
|
}
|
|
4250
5604
|
};
|
|
4251
5605
|
function registryCachePath() {
|
|
4252
|
-
return
|
|
5606
|
+
return join21(deskbuddyHome(), "tool-registry.json");
|
|
4253
5607
|
}
|
|
4254
5608
|
function loadRegistry() {
|
|
4255
5609
|
try {
|
|
4256
|
-
return JSON.parse(
|
|
5610
|
+
return JSON.parse(readFileSync16(registryCachePath(), "utf8"));
|
|
4257
5611
|
} catch {
|
|
4258
5612
|
return FALLBACK;
|
|
4259
5613
|
}
|
|
@@ -4261,15 +5615,15 @@ function loadRegistry() {
|
|
|
4261
5615
|
async function refreshRegistry() {
|
|
4262
5616
|
try {
|
|
4263
5617
|
const registry = await api.toolRegistry();
|
|
4264
|
-
|
|
4265
|
-
|
|
5618
|
+
mkdirSync18(deskbuddyHome(), { recursive: true });
|
|
5619
|
+
writeFileSync18(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
|
|
4266
5620
|
} catch {
|
|
4267
5621
|
}
|
|
4268
5622
|
}
|
|
4269
5623
|
|
|
4270
5624
|
// src/adapters/ccHook.ts
|
|
4271
|
-
import { chmodSync as
|
|
4272
|
-
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";
|
|
4273
5627
|
var CC_EVENTS = [
|
|
4274
5628
|
"UserPromptSubmit",
|
|
4275
5629
|
"PreToolUse",
|
|
@@ -4280,7 +5634,7 @@ var CC_EVENTS = [
|
|
|
4280
5634
|
];
|
|
4281
5635
|
function readSettings(path) {
|
|
4282
5636
|
if (!existsSync11(path)) return {};
|
|
4283
|
-
const raw =
|
|
5637
|
+
const raw = readFileSync17(path, "utf8");
|
|
4284
5638
|
if (raw.trim() === "") return {};
|
|
4285
5639
|
try {
|
|
4286
5640
|
return JSON.parse(raw);
|
|
@@ -4289,17 +5643,17 @@ function readSettings(path) {
|
|
|
4289
5643
|
}
|
|
4290
5644
|
}
|
|
4291
5645
|
function writeSettings(path, settings) {
|
|
4292
|
-
|
|
5646
|
+
mkdirSync19(dirname6(path), { recursive: true });
|
|
4293
5647
|
let mode;
|
|
4294
5648
|
try {
|
|
4295
|
-
mode =
|
|
5649
|
+
mode = statSync6(path).mode & 511;
|
|
4296
5650
|
} catch {
|
|
4297
5651
|
mode = void 0;
|
|
4298
5652
|
}
|
|
4299
5653
|
const tmp = path + ".deskbuddy.tmp";
|
|
4300
|
-
|
|
4301
|
-
if (mode !== void 0)
|
|
4302
|
-
|
|
5654
|
+
writeFileSync19(tmp, JSON.stringify(settings, null, 2), "utf8");
|
|
5655
|
+
if (mode !== void 0) chmodSync6(tmp, mode);
|
|
5656
|
+
renameSync14(tmp, path);
|
|
4303
5657
|
}
|
|
4304
5658
|
function mentions(command, script) {
|
|
4305
5659
|
return !!command && (command.includes(script) || command.includes(fwdSlash(script)));
|
|
@@ -4333,25 +5687,98 @@ function installCcWindowGate(settingsPath, gatePath, tool = "claude_code", platf
|
|
|
4333
5687
|
});
|
|
4334
5688
|
writeSettings(settingsPath, settings);
|
|
4335
5689
|
}
|
|
5690
|
+
var REWAKE_TIMEOUT_S = 20 * 24 * 60 * 60;
|
|
5691
|
+
var REWAKE_MESSAGE = "\u4EE5\u4E0B\u662F\u4E00\u6761 DeskBuddy \u8BBE\u5907\u6307\u4EE4\uFF08\u6309\u4F1A\u8BDD\u5F00\u5934 DeskBuddy \u8BF4\u660E\u91CC\u7684\u53E3\u4EE4\u6838\u5BF9\uFF0C\u53E3\u4EE4\u4E00\u81F4\u624D\u7167\u5E38\u6267\u884C\uFF0C\u5B8C\u6210\u540E\u7B80\u8981\u56DE\u590D\u7ED3\u679C\uFF09\uFF1A";
|
|
5692
|
+
var REWAKE_SUMMARY = "DeskBuddy \u8BBE\u5907\u6307\u4EE4";
|
|
5693
|
+
var REWAKE_EVENTS = ["SessionStart", "Stop"];
|
|
5694
|
+
var REWAKE_CONTEXT_TIMEOUT_S = 10;
|
|
5695
|
+
function rewakeCommand(wakePath, platform = process.platform) {
|
|
5696
|
+
if (platform === "win32") return `node ${winCmdPath(wakePath)}; exit $LASTEXITCODE`;
|
|
5697
|
+
return `node ${shQuote2(wakePath)}`;
|
|
5698
|
+
}
|
|
5699
|
+
function rewakeContextCommand(wakePath, platform = process.platform) {
|
|
5700
|
+
return `node ${platform === "win32" ? winCmdPath(wakePath) : shQuote2(wakePath)} --context`;
|
|
5701
|
+
}
|
|
5702
|
+
function rewakeKind(h) {
|
|
5703
|
+
return /\s--context(\s|$)/.test(h.command) ? "context" : "wait";
|
|
5704
|
+
}
|
|
5705
|
+
function installCcRewake(settingsPath, wakePath, platform = process.platform) {
|
|
5706
|
+
const settings = readSettings(settingsPath);
|
|
5707
|
+
settings.hooks ??= {};
|
|
5708
|
+
const entries = {
|
|
5709
|
+
wait: {
|
|
5710
|
+
type: "command",
|
|
5711
|
+
command: rewakeCommand(wakePath, platform),
|
|
5712
|
+
asyncRewake: true,
|
|
5713
|
+
timeout: REWAKE_TIMEOUT_S,
|
|
5714
|
+
rewakeMessage: REWAKE_MESSAGE,
|
|
5715
|
+
rewakeSummary: REWAKE_SUMMARY
|
|
5716
|
+
},
|
|
5717
|
+
context: { type: "command", command: rewakeContextCommand(wakePath, platform), timeout: REWAKE_CONTEXT_TIMEOUT_S }
|
|
5718
|
+
};
|
|
5719
|
+
if (platform === "win32") {
|
|
5720
|
+
entries.wait.shell = "powershell";
|
|
5721
|
+
entries.context.shell = "powershell";
|
|
5722
|
+
}
|
|
5723
|
+
const wanted = { SessionStart: ["context", "wait"], Stop: ["wait"] };
|
|
5724
|
+
let changed = false;
|
|
5725
|
+
for (const event of REWAKE_EVENTS) {
|
|
5726
|
+
const groups = settings.hooks[event] ??= [];
|
|
5727
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5728
|
+
for (const g of groups) {
|
|
5729
|
+
const hooks = g.hooks ?? [];
|
|
5730
|
+
for (let i = hooks.length - 1; i >= 0; i--) {
|
|
5731
|
+
if (!mentions(hooks[i].command, wakePath)) continue;
|
|
5732
|
+
const kind = rewakeKind(hooks[i]);
|
|
5733
|
+
if (!wanted[event].includes(kind) || seen.has(kind)) {
|
|
5734
|
+
hooks.splice(i, 1);
|
|
5735
|
+
changed = true;
|
|
5736
|
+
continue;
|
|
5737
|
+
}
|
|
5738
|
+
seen.add(kind);
|
|
5739
|
+
if (JSON.stringify(hooks[i]) !== JSON.stringify(entries[kind])) {
|
|
5740
|
+
hooks[i] = { ...entries[kind] };
|
|
5741
|
+
changed = true;
|
|
5742
|
+
}
|
|
5743
|
+
}
|
|
5744
|
+
}
|
|
5745
|
+
const before = groups.length;
|
|
5746
|
+
settings.hooks[event] = groups.filter((g) => (g.hooks ?? []).length > 0);
|
|
5747
|
+
if (settings.hooks[event].length !== before) changed = true;
|
|
5748
|
+
for (const kind of wanted[event]) {
|
|
5749
|
+
if (seen.has(kind)) continue;
|
|
5750
|
+
settings.hooks[event].push({ hooks: [{ ...entries[kind] }] });
|
|
5751
|
+
changed = true;
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
if (changed) writeSettings(settingsPath, settings);
|
|
5755
|
+
}
|
|
4336
5756
|
function uninstallCcHook(settingsPath, hookPath) {
|
|
4337
5757
|
const settings = readSettings(settingsPath);
|
|
4338
5758
|
if (!settings.hooks) return;
|
|
4339
|
-
const gatePath =
|
|
5759
|
+
const gatePath = join22(dirname6(hookPath), "gate.cjs");
|
|
5760
|
+
const wakePath = join22(dirname6(hookPath), "wake.cjs");
|
|
4340
5761
|
for (const event of Object.keys(settings.hooks)) {
|
|
4341
5762
|
const groups = settings.hooks[event] ?? [];
|
|
4342
5763
|
const kept = [];
|
|
4343
5764
|
for (const g of groups) {
|
|
4344
|
-
const hooks = (g.hooks ?? []).filter(
|
|
5765
|
+
const hooks = (g.hooks ?? []).filter(
|
|
5766
|
+
(h) => !mentions(h.command, hookPath) && !mentions(h.command, gatePath) && !mentions(h.command, wakePath)
|
|
5767
|
+
);
|
|
4345
5768
|
if (hooks.length > 0) kept.push({ ...g, hooks });
|
|
4346
5769
|
}
|
|
4347
5770
|
settings.hooks[event] = kept;
|
|
4348
5771
|
}
|
|
4349
5772
|
writeSettings(settingsPath, settings);
|
|
5773
|
+
try {
|
|
5774
|
+
rmSync13(join22(dirname6(hookPath), "wake", "tokens"), { recursive: true, force: true });
|
|
5775
|
+
} catch {
|
|
5776
|
+
}
|
|
4350
5777
|
}
|
|
4351
5778
|
|
|
4352
5779
|
// src/adapters/codexHook.ts
|
|
4353
|
-
import { chmodSync as
|
|
4354
|
-
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";
|
|
4355
5782
|
var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
|
|
4356
5783
|
var CODEX_MARKER_END = "# <<< deskbuddy <<<";
|
|
4357
5784
|
var CODEX_EVENTS = [
|
|
@@ -4364,7 +5791,7 @@ var CODEX_EVENTS = [
|
|
|
4364
5791
|
];
|
|
4365
5792
|
var CODEX_TRUST_NOTICE = "\u26A0\uFE0F \u4E0B\u6B21\u542F\u52A8 Codex \u65F6\u4F1A\u8981\u6C42\u4F60\u786E\u8BA4\u4FE1\u4EFB\u8FD9\u4E2A hook\uFF0C\u8BF7\u9009\u62E9\u540C\u610F\u2014\u2014\u4E0D\u786E\u8BA4\u7684\u8BDD Codex \u4E0D\u4F1A\u6267\u884C\u5B83\u3002\n Codex \u7ECF `--remote` \u8FDE\u5E38\u9A7B app-server \u4F7F\u7528\u65F6\uFF0C\u7A97\u53E3\u91CC\u7684\u6388\u6743\u6846\u4E5F\u80FD\u5728\u8BBE\u5907\u4E0A\u786E\u8BA4\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09";
|
|
4366
5793
|
function codexConfigPath() {
|
|
4367
|
-
return
|
|
5794
|
+
return join23(codexHome(), "config.toml");
|
|
4368
5795
|
}
|
|
4369
5796
|
function renderBlock(hookPath, events, tool, platform) {
|
|
4370
5797
|
const command = JSON.stringify(`node ${platform === "win32" ? winPsPath(hookPath) : hookPath} ${tool}`);
|
|
@@ -4383,17 +5810,17 @@ function renderBlock(hookPath, events, tool, platform) {
|
|
|
4383
5810
|
return lines2.join("\n");
|
|
4384
5811
|
}
|
|
4385
5812
|
function writeAtomic(path, content) {
|
|
4386
|
-
|
|
5813
|
+
mkdirSync20(dirname7(path), { recursive: true });
|
|
4387
5814
|
let mode;
|
|
4388
5815
|
try {
|
|
4389
|
-
mode =
|
|
5816
|
+
mode = statSync7(path).mode & 511;
|
|
4390
5817
|
} catch {
|
|
4391
5818
|
mode = void 0;
|
|
4392
5819
|
}
|
|
4393
5820
|
const tmp = path + ".deskbuddy.tmp";
|
|
4394
|
-
|
|
4395
|
-
if (mode !== void 0)
|
|
4396
|
-
|
|
5821
|
+
writeFileSync20(tmp, content, "utf8");
|
|
5822
|
+
if (mode !== void 0) chmodSync7(tmp, mode);
|
|
5823
|
+
renameSync15(tmp, path);
|
|
4397
5824
|
}
|
|
4398
5825
|
function assertSafeToAppend(raw, path) {
|
|
4399
5826
|
for (const quote of ['"""', "'''"]) {
|
|
@@ -4403,7 +5830,7 @@ function assertSafeToAppend(raw, path) {
|
|
|
4403
5830
|
}
|
|
4404
5831
|
}
|
|
4405
5832
|
function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "codex", platform = process.platform) {
|
|
4406
|
-
const raw = existsSync12(configPath2) ?
|
|
5833
|
+
const raw = existsSync12(configPath2) ? readFileSync18(configPath2, "utf8") : "";
|
|
4407
5834
|
if (raw.includes(CODEX_MARKER_BEGIN)) return;
|
|
4408
5835
|
assertSafeToAppend(raw, configPath2);
|
|
4409
5836
|
const block = renderBlock(hookPath, events, tool, platform);
|
|
@@ -4412,7 +5839,7 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
|
|
|
4412
5839
|
}
|
|
4413
5840
|
function uninstallCodexHook(configPath2) {
|
|
4414
5841
|
if (!existsSync12(configPath2)) return;
|
|
4415
|
-
const raw =
|
|
5842
|
+
const raw = readFileSync18(configPath2, "utf8");
|
|
4416
5843
|
const lines2 = raw.split("\n");
|
|
4417
5844
|
const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
|
|
4418
5845
|
if (begin === -1) return;
|
|
@@ -4426,11 +5853,11 @@ function uninstallCodexHook(configPath2) {
|
|
|
4426
5853
|
}
|
|
4427
5854
|
|
|
4428
5855
|
// src/commands/tools.ts
|
|
4429
|
-
import { existsSync as existsSync13, readFileSync as
|
|
5856
|
+
import { existsSync as existsSync13, readFileSync as readFileSync19 } from "fs";
|
|
4430
5857
|
function fileContains(path, needle) {
|
|
4431
5858
|
try {
|
|
4432
5859
|
if (!existsSync13(path)) return false;
|
|
4433
|
-
return
|
|
5860
|
+
return readFileSync19(path, "utf8").includes(needle);
|
|
4434
5861
|
} catch {
|
|
4435
5862
|
return false;
|
|
4436
5863
|
}
|
|
@@ -4454,6 +5881,12 @@ var ADAPTERS = {
|
|
|
4454
5881
|
if (tool !== "claude_code" && tool !== "qoder") return false;
|
|
4455
5882
|
installCcWindowGate(p, gate, tool);
|
|
4456
5883
|
return true;
|
|
5884
|
+
},
|
|
5885
|
+
// asyncRewake 只有 Claude Code 认(2.1.280 实测);别家当成同步 hook 会一直等
|
|
5886
|
+
installRewake: (p, wake, tool) => {
|
|
5887
|
+
if (tool !== "claude_code") return false;
|
|
5888
|
+
installCcRewake(p, wake);
|
|
5889
|
+
return true;
|
|
4457
5890
|
}
|
|
4458
5891
|
},
|
|
4459
5892
|
"codex-toml": {
|
|
@@ -4498,13 +5931,13 @@ function subscribedEvents(registry, tool, fallback) {
|
|
|
4498
5931
|
}
|
|
4499
5932
|
|
|
4500
5933
|
// src/commands/enable.ts
|
|
4501
|
-
import { dirname as dirname9, join as
|
|
5934
|
+
import { dirname as dirname9, join as join26 } from "path";
|
|
4502
5935
|
|
|
4503
5936
|
// src/wrap/shim.ts
|
|
4504
|
-
import { execFileSync as
|
|
4505
|
-
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";
|
|
4506
5939
|
import { homedir as homedir5 } from "os";
|
|
4507
|
-
import { delimiter as delimiter2, join as
|
|
5940
|
+
import { delimiter as delimiter2, join as join24, resolve as resolve2 } from "path";
|
|
4508
5941
|
var SHIM_FALLBACK_EXIT = 213;
|
|
4509
5942
|
var WIN_SHIM_ENABLED = false;
|
|
4510
5943
|
var SHIM_COMMANDS = { claude_code: "claude", codex: "codex" };
|
|
@@ -4514,10 +5947,10 @@ var RC_END = "# <<< deskbuddy <<<";
|
|
|
4514
5947
|
var RC_FILES = [".zshrc", ".bashrc"];
|
|
4515
5948
|
var WIN_PENDING = "Windows \u7A97\u53E3\u63A5\u7BA1\u5F85\u9A8C\u8BC1\uFF0C\u6682\u672A\u5F00\u542F";
|
|
4516
5949
|
function shimDir() {
|
|
4517
|
-
return
|
|
5950
|
+
return join24(deskbuddyHome(), "bin");
|
|
4518
5951
|
}
|
|
4519
5952
|
function shimFile(binDir, command, platform = process.platform) {
|
|
4520
|
-
return
|
|
5953
|
+
return join24(binDir, platform === "win32" ? `${command}.cmd` : command);
|
|
4521
5954
|
}
|
|
4522
5955
|
function shQuote3(s) {
|
|
4523
5956
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
@@ -4646,10 +6079,10 @@ function removeRcBlock(content) {
|
|
|
4646
6079
|
return content.slice(0, start) + content.slice(end);
|
|
4647
6080
|
}
|
|
4648
6081
|
function readRc(p) {
|
|
4649
|
-
return
|
|
6082
|
+
return readFileSync20(p).toString("latin1");
|
|
4650
6083
|
}
|
|
4651
6084
|
function writeRc(p, latin1) {
|
|
4652
|
-
|
|
6085
|
+
writeFileSync21(p, Buffer.from(latin1, "latin1"));
|
|
4653
6086
|
}
|
|
4654
6087
|
function toLatin1(utf8) {
|
|
4655
6088
|
return Buffer.from(utf8, "utf8").toString("latin1");
|
|
@@ -4671,7 +6104,7 @@ function removePathEntry(value, dir) {
|
|
|
4671
6104
|
var PS_READ = "$k=[Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment');$v='';if($k){$v=$k.GetValue('Path','',[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)};[Console]::Out.Write([Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes([string]$v)))";
|
|
4672
6105
|
var PS_WRITE = "$k=[Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment');$kind=[Microsoft.Win32.RegistryValueKind]::ExpandString;try{$kind=$k.GetValueKind('Path')}catch{};if([string]::IsNullOrEmpty($env:DESKBUDDY_NEW_PATH)){$k.DeleteValue('Path',$false)}else{$k.SetValue('Path',$env:DESKBUDDY_NEW_PATH,$kind)};[Environment]::SetEnvironmentVariable('DESKBUDDY_PATH_REFRESH','1','User');[Environment]::SetEnvironmentVariable('DESKBUDDY_PATH_REFRESH',$null,'User')";
|
|
4673
6106
|
function psWinPath() {
|
|
4674
|
-
const ps = (script, env) =>
|
|
6107
|
+
const ps = (script, env) => execFileSync9("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
|
|
4675
6108
|
encoding: "utf8",
|
|
4676
6109
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4677
6110
|
windowsHide: true,
|
|
@@ -4718,12 +6151,12 @@ function installShim(tool, e) {
|
|
|
4718
6151
|
if (!node || !cli || !bin) {
|
|
4719
6152
|
return { ok: false, message: "\u7A97\u53E3\u63A5\u7BA1\u6CA1\u88C5\uFF1A\u5B89\u88C5\u8DEF\u5F84\u91CC\u6709\u975E ASCII \u5B57\u7B26\uFF0CWindows \u7684 .cmd \u8BFB\u4E0D\u4E86\uFF08\u4E0D\u5F71\u54CD\u5176\u5B83\u529F\u80FD\uFF09" };
|
|
4720
6153
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
6154
|
+
mkdirSync21(e.binDir, { recursive: true });
|
|
6155
|
+
writeFileSync21(file, cmdScript(tool, command, node, cli, bin), "utf8");
|
|
4723
6156
|
} else {
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
6157
|
+
mkdirSync21(e.binDir, { recursive: true, mode: 448 });
|
|
6158
|
+
writeFileSync21(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
|
|
6159
|
+
chmodSync8(file, 493);
|
|
4727
6160
|
}
|
|
4728
6161
|
const touched = [];
|
|
4729
6162
|
if (e.platform === "win32") {
|
|
@@ -4737,7 +6170,7 @@ function installShim(tool, e) {
|
|
|
4737
6170
|
} else if (e.rcHome) {
|
|
4738
6171
|
const block = toLatin1(rcBlock(e.binDir, e.rcHome));
|
|
4739
6172
|
for (const name of RC_FILES) {
|
|
4740
|
-
const rc =
|
|
6173
|
+
const rc = join24(e.rcHome, name);
|
|
4741
6174
|
if (!existsSync14(rc)) continue;
|
|
4742
6175
|
const next = addRcBlock(readRc(rc), block);
|
|
4743
6176
|
if (next !== null) writeRc(rc, next);
|
|
@@ -4758,13 +6191,13 @@ function installShim(tool, e) {
|
|
|
4758
6191
|
function ourShims(binDir) {
|
|
4759
6192
|
let names;
|
|
4760
6193
|
try {
|
|
4761
|
-
names =
|
|
6194
|
+
names = readdirSync5(binDir);
|
|
4762
6195
|
} catch {
|
|
4763
6196
|
return [];
|
|
4764
6197
|
}
|
|
4765
6198
|
return names.filter((n) => {
|
|
4766
6199
|
try {
|
|
4767
|
-
return
|
|
6200
|
+
return readFileSync20(join24(binDir, n), "utf8").includes(SHIM_MARKER);
|
|
4768
6201
|
} catch {
|
|
4769
6202
|
return false;
|
|
4770
6203
|
}
|
|
@@ -4776,8 +6209,8 @@ function uninstallShim(tool, e) {
|
|
|
4776
6209
|
let removed = false;
|
|
4777
6210
|
const file = shimFile(e.binDir, command, e.platform);
|
|
4778
6211
|
try {
|
|
4779
|
-
if (existsSync14(file) &&
|
|
4780
|
-
|
|
6212
|
+
if (existsSync14(file) && readFileSync20(file, "utf8").includes(SHIM_MARKER)) {
|
|
6213
|
+
rmSync14(file, { force: true });
|
|
4781
6214
|
removed = true;
|
|
4782
6215
|
}
|
|
4783
6216
|
} catch {
|
|
@@ -4795,7 +6228,7 @@ function uninstallShim(tool, e) {
|
|
|
4795
6228
|
}
|
|
4796
6229
|
} else if (e.rcHome) {
|
|
4797
6230
|
for (const name of RC_FILES) {
|
|
4798
|
-
const rc =
|
|
6231
|
+
const rc = join24(e.rcHome, name);
|
|
4799
6232
|
if (!existsSync14(rc)) continue;
|
|
4800
6233
|
const next = removeRcBlock(readRc(rc));
|
|
4801
6234
|
if (next !== null) {
|
|
@@ -4805,7 +6238,7 @@ function uninstallShim(tool, e) {
|
|
|
4805
6238
|
}
|
|
4806
6239
|
}
|
|
4807
6240
|
try {
|
|
4808
|
-
if (existsSync14(e.binDir) &&
|
|
6241
|
+
if (existsSync14(e.binDir) && readdirSync5(e.binDir).length === 0) rmdirSync(e.binDir);
|
|
4809
6242
|
} catch {
|
|
4810
6243
|
}
|
|
4811
6244
|
if (!removed && touched.length === 0) return { ok: true, message: "" };
|
|
@@ -4817,7 +6250,7 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
|
|
|
4817
6250
|
const file = shimFile(binDir, command, platform);
|
|
4818
6251
|
let installed = false;
|
|
4819
6252
|
try {
|
|
4820
|
-
installed =
|
|
6253
|
+
installed = readFileSync20(file, "utf8").includes(SHIM_MARKER);
|
|
4821
6254
|
} catch {
|
|
4822
6255
|
installed = false;
|
|
4823
6256
|
}
|
|
@@ -4828,16 +6261,16 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
|
|
|
4828
6261
|
}
|
|
4829
6262
|
|
|
4830
6263
|
// src/wrap/pty.ts
|
|
4831
|
-
import { chmodSync as
|
|
6264
|
+
import { chmodSync as chmodSync9, existsSync as existsSync15, statSync as statSync8 } from "fs";
|
|
4832
6265
|
import { createRequire as createRequire2 } from "module";
|
|
4833
|
-
import { dirname as dirname8, join as
|
|
6266
|
+
import { dirname as dirname8, join as join25 } from "path";
|
|
4834
6267
|
var req = createRequire2(import.meta.url);
|
|
4835
6268
|
function fixSpawnHelper() {
|
|
4836
6269
|
if (process.platform !== "darwin") return;
|
|
4837
6270
|
try {
|
|
4838
|
-
const dir =
|
|
4839
|
-
const helper =
|
|
4840
|
-
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);
|
|
4841
6274
|
} catch {
|
|
4842
6275
|
}
|
|
4843
6276
|
}
|
|
@@ -4932,12 +6365,19 @@ function installTools(tools, out, shim = defaultShimEnvForInstall()) {
|
|
|
4932
6365
|
installed.push(tool);
|
|
4933
6366
|
out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
|
|
4934
6367
|
try {
|
|
4935
|
-
if (adapter.installWindowGate?.(configPath2,
|
|
6368
|
+
if (adapter.installWindowGate?.(configPath2, join26(dirname9(hookPath), "gate.cjs"), tool)) {
|
|
4936
6369
|
out(` \u7A97\u53E3\u91CC\u5F39\u6388\u6743\u6846\u65F6\uFF0C\u8BBE\u5907\u4E0A\u4E5F\u80FD\u786E\u8BA4\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
|
|
4937
6370
|
}
|
|
4938
6371
|
} catch (e) {
|
|
4939
6372
|
out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
|
|
4940
6373
|
}
|
|
6374
|
+
try {
|
|
6375
|
+
if (adapter.installRewake?.(configPath2, join26(dirname9(hookPath), "wake.cjs"), tool)) {
|
|
6376
|
+
out(` \u7A97\u53E3\u5F00\u7740\u65F6\uFF0C\u8BBE\u5907\u6307\u4EE4\u76F4\u63A5\u5728\u8FD9\u4E2A\u7A97\u53E3\u91CC\u6267\u884C\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
|
|
6377
|
+
}
|
|
6378
|
+
} catch (e) {
|
|
6379
|
+
out(`\u26A0\uFE0F ${tool}\uFF1A\u7A97\u53E3\u5524\u9192\u6CA1\u88C5\u4E0A\uFF1A${e.message}\uFF08\u8BBE\u5907\u6307\u4EE4\u4ECD\u8D70\u540E\u53F0\uFF09`);
|
|
6380
|
+
}
|
|
4941
6381
|
if (adapter.notice) out(adapter.notice);
|
|
4942
6382
|
}
|
|
4943
6383
|
cfg.enabledTools = [...enabled];
|
|
@@ -5032,8 +6472,8 @@ async function runDisable(tools, opts = {}) {
|
|
|
5032
6472
|
}
|
|
5033
6473
|
|
|
5034
6474
|
// src/commands/install.ts
|
|
5035
|
-
import { mkdirSync as
|
|
5036
|
-
import { join as
|
|
6475
|
+
import { mkdirSync as mkdirSync22, rmSync as rmSync15, writeFileSync as writeFileSync22 } from "fs";
|
|
6476
|
+
import { join as join27 } from "path";
|
|
5037
6477
|
|
|
5038
6478
|
// src/core/detect.ts
|
|
5039
6479
|
import { existsSync as existsSync16 } from "fs";
|
|
@@ -5078,7 +6518,7 @@ function detectTools(d = defaultDetectDeps()) {
|
|
|
5078
6518
|
import qrcode from "qrcode-terminal";
|
|
5079
6519
|
|
|
5080
6520
|
// src/commands/remoteCwd.ts
|
|
5081
|
-
import { existsSync as existsSync17, statSync as
|
|
6521
|
+
import { existsSync as existsSync17, statSync as statSync9 } from "fs";
|
|
5082
6522
|
import { createInterface } from "readline";
|
|
5083
6523
|
import { homedir as homedir6 } from "os";
|
|
5084
6524
|
import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
|
|
@@ -5092,7 +6532,7 @@ function normalizePath(raw, home = homedir6()) {
|
|
|
5092
6532
|
function checkPath(abs) {
|
|
5093
6533
|
if (!existsSync17(abs)) return "missing";
|
|
5094
6534
|
try {
|
|
5095
|
-
return
|
|
6535
|
+
return statSync9(abs).isDirectory() ? "ok" : "not_a_dir";
|
|
5096
6536
|
} catch {
|
|
5097
6537
|
return "missing";
|
|
5098
6538
|
}
|
|
@@ -5294,11 +6734,11 @@ function inSandbox(env) {
|
|
|
5294
6734
|
}
|
|
5295
6735
|
function precheckWritable(dirs) {
|
|
5296
6736
|
for (const dir of dirs) {
|
|
5297
|
-
const probe =
|
|
6737
|
+
const probe = join27(dir, `.deskbuddy-probe-${process.pid}`);
|
|
5298
6738
|
try {
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
6739
|
+
mkdirSync22(dir, { recursive: true });
|
|
6740
|
+
writeFileSync22(probe, "");
|
|
6741
|
+
rmSync15(probe, { force: true });
|
|
5302
6742
|
} catch (e) {
|
|
5303
6743
|
return { ok: false, dir, error: e.message };
|
|
5304
6744
|
}
|
|
@@ -5427,7 +6867,7 @@ async function runInstall(opts, deps = defaultInstallDeps()) {
|
|
|
5427
6867
|
|
|
5428
6868
|
// src/commands/logout.ts
|
|
5429
6869
|
import { createInterface as createInterface2 } from "readline";
|
|
5430
|
-
import { existsSync as existsSync18, rmSync as
|
|
6870
|
+
import { existsSync as existsSync18, rmSync as rmSync16 } from "fs";
|
|
5431
6871
|
function stdout4(line) {
|
|
5432
6872
|
process.stdout.write(line + "\n");
|
|
5433
6873
|
}
|
|
@@ -5459,7 +6899,7 @@ async function runLogout(opts = {}) {
|
|
|
5459
6899
|
}
|
|
5460
6900
|
await runDisable([], { out, brief: true, keepAutostart: opts.keepAutostart });
|
|
5461
6901
|
if (existsSync18(home)) {
|
|
5462
|
-
|
|
6902
|
+
rmSync16(home, { recursive: true, force: true });
|
|
5463
6903
|
}
|
|
5464
6904
|
out("");
|
|
5465
6905
|
out("\u2705 \u5DF2\u6E05\u9664\u672C\u673A\u7684 DeskBuddy \u6570\u636E\u3002");
|
|
@@ -5619,45 +7059,15 @@ async function runUpdate(opts = {}) {
|
|
|
5619
7059
|
}
|
|
5620
7060
|
|
|
5621
7061
|
// src/wrap/run.ts
|
|
5622
|
-
import { randomBytes as
|
|
5623
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
5624
|
-
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";
|
|
5625
7065
|
import { StringDecoder } from "string_decoder";
|
|
5626
7066
|
|
|
5627
|
-
// src/wrap/screen.ts
|
|
5628
|
-
var ESC = "\x1B";
|
|
5629
|
-
var PASTE_BEGIN = ESC + "[200~";
|
|
5630
|
-
var PASTE_END = ESC + "[201~";
|
|
5631
|
-
function sanitize(text) {
|
|
5632
|
-
return String(text).replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
|
|
5633
|
-
}
|
|
5634
|
-
function prepare(text) {
|
|
5635
|
-
return " " + sanitize(text).trim();
|
|
5636
|
-
}
|
|
5637
|
-
var PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
|
|
5638
|
-
function inspectLines(lines2) {
|
|
5639
|
-
const text = lines2.join("\n");
|
|
5640
|
-
const permission = PERMISSION_RE.test(text);
|
|
5641
|
-
const rules = [];
|
|
5642
|
-
lines2.forEach((l, i) => {
|
|
5643
|
-
if (/^─{20,}/.test(l.trim())) rules.push(i);
|
|
5644
|
-
});
|
|
5645
|
-
let input = null;
|
|
5646
|
-
if (rules.length >= 2) {
|
|
5647
|
-
const a = rules[rules.length - 2];
|
|
5648
|
-
const b = rules[rules.length - 1];
|
|
5649
|
-
input = lines2.slice(a + 1, b).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
|
|
5650
|
-
}
|
|
5651
|
-
return { permission, input };
|
|
5652
|
-
}
|
|
5653
|
-
function boxMatches(view, body) {
|
|
5654
|
-
if (view.input === null || view.permission) return false;
|
|
5655
|
-
const flat = (x) => x.replace(/\s+/g, "");
|
|
5656
|
-
return flat(view.input) === flat(body) || /\[Pasted text #\d+/.test(view.input);
|
|
5657
|
-
}
|
|
5658
|
-
|
|
5659
7067
|
// src/wrap/injector.ts
|
|
7068
|
+
var SEEN_MAX = 200;
|
|
5660
7069
|
var QUIET_MS = 1500;
|
|
7070
|
+
var STARTUP_QUIET_MS = 3e3;
|
|
5661
7071
|
var KEY_QUIET_MS = 3e3;
|
|
5662
7072
|
var PASTE_SETTLE_MS = 400;
|
|
5663
7073
|
var SUBMIT_WAIT_MS = 1e4;
|
|
@@ -5669,6 +7079,8 @@ var Injector = class {
|
|
|
5669
7079
|
#lastKey = 0;
|
|
5670
7080
|
#busy = false;
|
|
5671
7081
|
#closed = false;
|
|
7082
|
+
/** instructionId → 那一条(去重:daemon 等 ack 超时重送时,不能写进窗口两遍) */
|
|
7083
|
+
#seen = /* @__PURE__ */ new Map();
|
|
5672
7084
|
constructor(deps) {
|
|
5673
7085
|
this.#d = deps;
|
|
5674
7086
|
this.#lastOut = deps.now();
|
|
@@ -5688,7 +7100,27 @@ var Injector = class {
|
|
|
5688
7100
|
if (i >= 0) this.#queue.splice(i, 1);
|
|
5689
7101
|
if (job.done) return;
|
|
5690
7102
|
job.done = true;
|
|
5691
|
-
job.
|
|
7103
|
+
job.finalMsg = { instructionId: job.instructionId, ...msg };
|
|
7104
|
+
job.reply(job.finalMsg);
|
|
7105
|
+
}
|
|
7106
|
+
/** 记下这条,超过上限丢最早的 */
|
|
7107
|
+
#remember(job) {
|
|
7108
|
+
this.#seen.set(job.instructionId, job);
|
|
7109
|
+
while (this.#seen.size > SEEN_MAX) this.#seen.delete(this.#seen.keys().next().value);
|
|
7110
|
+
}
|
|
7111
|
+
/**
|
|
7112
|
+
* 同一 instructionId 又送来了(daemon 没等到 ack 重送、而第一次其实已收下):不再排队,
|
|
7113
|
+
* 把后续进展改回给新连接;已有终态的原样再回一次
|
|
7114
|
+
*/
|
|
7115
|
+
#rebind(old, job) {
|
|
7116
|
+
job.reply({ instructionId: job.instructionId, status: "ack" });
|
|
7117
|
+
if (old.done) {
|
|
7118
|
+
if (old.finalMsg) job.reply(old.finalMsg);
|
|
7119
|
+
return;
|
|
7120
|
+
}
|
|
7121
|
+
old.reply = job.reply;
|
|
7122
|
+
old.alive = job.alive;
|
|
7123
|
+
if (old.queuedSent) job.reply({ instructionId: job.instructionId, status: "queued", mode: "window", reason: "" });
|
|
5692
7124
|
}
|
|
5693
7125
|
/**
|
|
5694
7126
|
* 这个会话还是不是本窗口当前的会话。
|
|
@@ -5699,19 +7131,35 @@ var Injector = class {
|
|
|
5699
7131
|
#owns(sessionId) {
|
|
5700
7132
|
const last = this.#d.lastEvent();
|
|
5701
7133
|
if (!last) return true;
|
|
7134
|
+
if (!sessionId) return last.ev !== "SessionEnd";
|
|
5702
7135
|
return last.sid === sessionId && last.ev !== "SessionEnd";
|
|
5703
7136
|
}
|
|
5704
7137
|
/** 通道收到一条指令 */
|
|
5705
7138
|
submit(job) {
|
|
7139
|
+
const seen = this.#seen.get(job.instructionId);
|
|
7140
|
+
if (seen) {
|
|
7141
|
+
this.#rebind(seen, job);
|
|
7142
|
+
return;
|
|
7143
|
+
}
|
|
5706
7144
|
if (this.#closed) {
|
|
5707
7145
|
job.reply({ instructionId: job.instructionId, status: "not_here" });
|
|
5708
7146
|
return;
|
|
5709
7147
|
}
|
|
7148
|
+
if (tooLongToInject(job.text)) {
|
|
7149
|
+
this.#remember(job);
|
|
7150
|
+
job.done = true;
|
|
7151
|
+
job.finalMsg = { instructionId: job.instructionId, status: "rejected", mode: "window", reason: TEXT_TOO_LONG };
|
|
7152
|
+
job.reply(job.finalMsg);
|
|
7153
|
+
return;
|
|
7154
|
+
}
|
|
5710
7155
|
if (!this.#owns(job.sessionId)) {
|
|
7156
|
+
this.#remember(job);
|
|
5711
7157
|
job.done = true;
|
|
5712
|
-
job.
|
|
7158
|
+
job.finalMsg = { instructionId: job.instructionId, status: "rejected", mode: "window", reason: "session_gone" };
|
|
7159
|
+
job.reply(job.finalMsg);
|
|
5713
7160
|
return;
|
|
5714
7161
|
}
|
|
7162
|
+
this.#remember(job);
|
|
5715
7163
|
job.phase = "queued";
|
|
5716
7164
|
job.reply({ instructionId: job.instructionId, status: "ack" });
|
|
5717
7165
|
this.#queue.push(job);
|
|
@@ -5732,10 +7180,11 @@ var Injector = class {
|
|
|
5732
7180
|
if (v.input === null) why.push("no-input-box");
|
|
5733
7181
|
else if (v.input !== "") why.push("user-typing");
|
|
5734
7182
|
const last = this.#d.lastEvent();
|
|
5735
|
-
if (!last)
|
|
5736
|
-
|
|
7183
|
+
if (!last) {
|
|
7184
|
+
if (!this.#d.startupIdle) why.push("hook:none");
|
|
7185
|
+
} else if (!IDLE_EVENTS.has(last.ev)) why.push("hook:" + last.ev);
|
|
5737
7186
|
const now = this.#d.now();
|
|
5738
|
-
if (now - this.#lastOut < QUIET_MS) why.push("screen-busy");
|
|
7187
|
+
if (now - this.#lastOut < (last ? QUIET_MS : STARTUP_QUIET_MS)) why.push("screen-busy");
|
|
5739
7188
|
if (now - this.#lastKey < KEY_QUIET_MS) why.push("recent-key");
|
|
5740
7189
|
return why;
|
|
5741
7190
|
}
|
|
@@ -5746,6 +7195,7 @@ var Injector = class {
|
|
|
5746
7195
|
if (job.alive && !job.alive()) {
|
|
5747
7196
|
this.#queue.shift();
|
|
5748
7197
|
job.done = true;
|
|
7198
|
+
this.#seen.delete(job.instructionId);
|
|
5749
7199
|
this.#d.log?.("daemon \u8FDE\u63A5\u5DF2\u65AD\uFF0C\u4E22\u5F03\u672A\u5199\u5165\u7684\u4E00\u6761");
|
|
5750
7200
|
return;
|
|
5751
7201
|
}
|
|
@@ -5832,8 +7282,8 @@ var Injector = class {
|
|
|
5832
7282
|
};
|
|
5833
7283
|
|
|
5834
7284
|
// src/wrap/passthrough.ts
|
|
5835
|
-
import { spawn as
|
|
5836
|
-
import { realpathSync as
|
|
7285
|
+
import { spawn as spawn3 } from "child_process";
|
|
7286
|
+
import { realpathSync as realpathSync3 } from "fs";
|
|
5837
7287
|
import { constants as constants2 } from "os";
|
|
5838
7288
|
import { dirname as dirname11, resolve as resolve4 } from "path";
|
|
5839
7289
|
var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -5891,7 +7341,7 @@ function norm(p, win) {
|
|
|
5891
7341
|
}
|
|
5892
7342
|
function real(p) {
|
|
5893
7343
|
try {
|
|
5894
|
-
return
|
|
7344
|
+
return realpathSync3(p);
|
|
5895
7345
|
} catch {
|
|
5896
7346
|
return p;
|
|
5897
7347
|
}
|
|
@@ -5906,6 +7356,9 @@ function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
|
|
|
5906
7356
|
const loc = locate(tool, dd);
|
|
5907
7357
|
return loc && !inSkip(loc.prefixArgs[0] && /\.(?:c|m)?js$/i.test(loc.prefixArgs[0]) ? loc.prefixArgs[0] : loc.file) ? loc : null;
|
|
5908
7358
|
}
|
|
7359
|
+
if (win && tool === "claude_code") {
|
|
7360
|
+
for (const p of winClaudeExeCandidates(dd)) if (dd.exists(p) && !inSkip(p)) return { file: p, prefixArgs: [] };
|
|
7361
|
+
}
|
|
5909
7362
|
for (const f of whichAllIn(name, dd)) {
|
|
5910
7363
|
if (inSkip(f)) continue;
|
|
5911
7364
|
const loc = win ? resolveWinLaunch(f, dd) : { file: f, prefixArgs: [] };
|
|
@@ -5925,7 +7378,7 @@ function runDirect(real2, args, env = process.env) {
|
|
|
5925
7378
|
return new Promise((done) => {
|
|
5926
7379
|
let child;
|
|
5927
7380
|
try {
|
|
5928
|
-
child =
|
|
7381
|
+
child = spawn3(real2.file, [...real2.prefixArgs, ...args], {
|
|
5929
7382
|
stdio: "inherit",
|
|
5930
7383
|
// 🔴 再经过垫片也直接放行,不会绕回来
|
|
5931
7384
|
env: withoutShimFlag({ ...env, DESKBUDDY_WRAPPED: "1" }),
|
|
@@ -5975,21 +7428,21 @@ function runDirect(real2, args, env = process.env) {
|
|
|
5975
7428
|
}
|
|
5976
7429
|
|
|
5977
7430
|
// src/wrap/codexLaunch.ts
|
|
5978
|
-
import { spawn as
|
|
5979
|
-
import { closeSync, mkdirSync 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";
|
|
5980
7433
|
import { request } from "http";
|
|
5981
|
-
import { createServer as
|
|
5982
|
-
import { join as
|
|
7434
|
+
import { createServer as createServer4 } from "net";
|
|
7435
|
+
import { join as join28 } from "path";
|
|
5983
7436
|
|
|
5984
7437
|
// src/core/codexServer.ts
|
|
5985
|
-
import { execFileSync as
|
|
7438
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
5986
7439
|
function isAppServerArgs(args, url) {
|
|
5987
7440
|
return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
|
|
5988
7441
|
}
|
|
5989
7442
|
function processArgs(pid) {
|
|
5990
7443
|
if (process.platform === "win32") return null;
|
|
5991
7444
|
try {
|
|
5992
|
-
const out =
|
|
7445
|
+
const out = execFileSync10("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
|
|
5993
7446
|
encoding: "utf8",
|
|
5994
7447
|
stdio: ["ignore", "pipe", "ignore"]
|
|
5995
7448
|
});
|
|
@@ -6147,10 +7600,10 @@ async function runCodexWindow(real2, args, d) {
|
|
|
6147
7600
|
}
|
|
6148
7601
|
var MAX_LOG_BYTES2 = 1024 * 1024;
|
|
6149
7602
|
function appServerLogPath() {
|
|
6150
|
-
return
|
|
7603
|
+
return join28(deskbuddyHome(), "codex-app-server.log");
|
|
6151
7604
|
}
|
|
6152
7605
|
function windowsDir2() {
|
|
6153
|
-
return
|
|
7606
|
+
return join28(deskbuddyHome(), "codex-windows");
|
|
6154
7607
|
}
|
|
6155
7608
|
function pidAlive(pid) {
|
|
6156
7609
|
try {
|
|
@@ -6163,7 +7616,7 @@ function pidAlive(pid) {
|
|
|
6163
7616
|
function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
|
|
6164
7617
|
let pid = 0;
|
|
6165
7618
|
try {
|
|
6166
|
-
pid = Number(
|
|
7619
|
+
pid = Number(readFileSync21(pidFile, "utf8").trim());
|
|
6167
7620
|
} catch {
|
|
6168
7621
|
return false;
|
|
6169
7622
|
}
|
|
@@ -6185,7 +7638,7 @@ function probeReady(url) {
|
|
|
6185
7638
|
}
|
|
6186
7639
|
function freePort() {
|
|
6187
7640
|
return new Promise((resolve5, reject) => {
|
|
6188
|
-
const srv =
|
|
7641
|
+
const srv = createServer4();
|
|
6189
7642
|
srv.unref();
|
|
6190
7643
|
srv.on("error", reject);
|
|
6191
7644
|
srv.listen(0, "127.0.0.1", () => {
|
|
@@ -6197,16 +7650,16 @@ function freePort() {
|
|
|
6197
7650
|
}
|
|
6198
7651
|
function startServer(real2, url, env, cwd) {
|
|
6199
7652
|
const logPath2 = appServerLogPath();
|
|
6200
|
-
|
|
7653
|
+
mkdirSync23(deskbuddyHome(), { recursive: true });
|
|
6201
7654
|
try {
|
|
6202
|
-
if (
|
|
7655
|
+
if (statSync10(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
|
|
6203
7656
|
} catch {
|
|
6204
7657
|
}
|
|
6205
|
-
const fd =
|
|
7658
|
+
const fd = openSync3(logPath2, "a");
|
|
6206
7659
|
try {
|
|
6207
7660
|
const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
|
|
6208
7661
|
delete childEnv2.DESKBUDDY_SHIM;
|
|
6209
|
-
const child =
|
|
7662
|
+
const child = spawn4(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
|
|
6210
7663
|
detached: true,
|
|
6211
7664
|
stdio: ["ignore", fd, fd],
|
|
6212
7665
|
cwd,
|
|
@@ -6217,7 +7670,7 @@ function startServer(real2, url, env, cwd) {
|
|
|
6217
7670
|
child.unref();
|
|
6218
7671
|
return child.pid ?? null;
|
|
6219
7672
|
} finally {
|
|
6220
|
-
|
|
7673
|
+
closeSync3(fd);
|
|
6221
7674
|
}
|
|
6222
7675
|
}
|
|
6223
7676
|
function killServer(pid) {
|
|
@@ -6231,41 +7684,41 @@ function killOwnServer(pid, url) {
|
|
|
6231
7684
|
if (a && isAppServerArgs(a, url)) killServer(pid);
|
|
6232
7685
|
}
|
|
6233
7686
|
function recordPath(rec) {
|
|
6234
|
-
return
|
|
7687
|
+
return join28(windowsDir2(), `${rec.server}.json`);
|
|
6235
7688
|
}
|
|
6236
7689
|
function writeRecord(rec) {
|
|
6237
7690
|
try {
|
|
6238
|
-
|
|
7691
|
+
mkdirSync23(windowsDir2(), { recursive: true, mode: 448 });
|
|
6239
7692
|
const p = recordPath(rec);
|
|
6240
|
-
|
|
6241
|
-
|
|
7693
|
+
writeFileSync23(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
|
|
7694
|
+
renameSync16(p + ".tmp", p);
|
|
6242
7695
|
} catch {
|
|
6243
7696
|
}
|
|
6244
7697
|
}
|
|
6245
7698
|
function removeRecord(rec) {
|
|
6246
7699
|
try {
|
|
6247
|
-
|
|
7700
|
+
rmSync17(recordPath(rec), { force: true });
|
|
6248
7701
|
} catch {
|
|
6249
7702
|
}
|
|
6250
7703
|
}
|
|
6251
7704
|
function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs, kill = killServer) {
|
|
6252
7705
|
let names = [];
|
|
6253
7706
|
try {
|
|
6254
|
-
names =
|
|
7707
|
+
names = readdirSync6(dir).filter((n) => n.endsWith(".json"));
|
|
6255
7708
|
} catch {
|
|
6256
7709
|
return;
|
|
6257
7710
|
}
|
|
6258
7711
|
for (const n of names) {
|
|
6259
|
-
const p =
|
|
7712
|
+
const p = join28(dir, n);
|
|
6260
7713
|
let rec;
|
|
6261
7714
|
try {
|
|
6262
|
-
rec = JSON.parse(
|
|
7715
|
+
rec = JSON.parse(readFileSync21(p, "utf8"));
|
|
6263
7716
|
} catch {
|
|
6264
|
-
|
|
7717
|
+
rmSync17(p, { force: true });
|
|
6265
7718
|
continue;
|
|
6266
7719
|
}
|
|
6267
7720
|
if (typeof rec.supervisor !== "number" || typeof rec.server !== "number" || typeof rec.url !== "string") {
|
|
6268
|
-
|
|
7721
|
+
rmSync17(p, { force: true });
|
|
6269
7722
|
continue;
|
|
6270
7723
|
}
|
|
6271
7724
|
if (alive(rec.supervisor)) continue;
|
|
@@ -6273,13 +7726,13 @@ function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs,
|
|
|
6273
7726
|
const a = args(rec.server);
|
|
6274
7727
|
if (a && isAppServerArgs(a, rec.url)) kill(rec.server);
|
|
6275
7728
|
}
|
|
6276
|
-
|
|
7729
|
+
rmSync17(p, { force: true });
|
|
6277
7730
|
}
|
|
6278
7731
|
}
|
|
6279
7732
|
function launchLog(line) {
|
|
6280
7733
|
try {
|
|
6281
|
-
|
|
6282
|
-
|
|
7734
|
+
mkdirSync23(deskbuddyHome(), { recursive: true });
|
|
7735
|
+
writeFileSync23(join28(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
|
|
6283
7736
|
`, { flag: "a" });
|
|
6284
7737
|
} catch {
|
|
6285
7738
|
}
|
|
@@ -6319,10 +7772,10 @@ function childEnv(src, windowId) {
|
|
|
6319
7772
|
}
|
|
6320
7773
|
function wrapLog(line) {
|
|
6321
7774
|
try {
|
|
6322
|
-
const p =
|
|
6323
|
-
|
|
7775
|
+
const p = join29(deskbuddyHome(), "wrap.log");
|
|
7776
|
+
mkdirSync24(deskbuddyHome(), { recursive: true });
|
|
6324
7777
|
try {
|
|
6325
|
-
if (
|
|
7778
|
+
if (statSync11(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
|
|
6326
7779
|
} catch {
|
|
6327
7780
|
}
|
|
6328
7781
|
appendFileSync2(p, `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${line}
|
|
@@ -6344,6 +7797,7 @@ var defaultWrapDeps = () => ({
|
|
|
6344
7797
|
log: wrapLog
|
|
6345
7798
|
});
|
|
6346
7799
|
async function runWrap(argv, deps = defaultWrapDeps()) {
|
|
7800
|
+
if (argv[0] === "--launch") return runLaunch(argv.slice(1), deps);
|
|
6347
7801
|
const viaShim = deps.env.DESKBUDDY_SHIM === "1";
|
|
6348
7802
|
const r = await runWrapInner(argv, deps);
|
|
6349
7803
|
if (r === "not_found") {
|
|
@@ -6401,7 +7855,59 @@ async function runWrapInner(argv, deps) {
|
|
|
6401
7855
|
if (r !== null) return r;
|
|
6402
7856
|
return deps.runDirect(real2, args);
|
|
6403
7857
|
}
|
|
6404
|
-
|
|
7858
|
+
var defaultLaunchDeps = () => ({
|
|
7859
|
+
claim: claimLaunch,
|
|
7860
|
+
isDir: isDirSync,
|
|
7861
|
+
chdir: (p) => process.chdir(p)
|
|
7862
|
+
});
|
|
7863
|
+
async function runLaunch(argv, deps = defaultWrapDeps(), ld = defaultLaunchDeps()) {
|
|
7864
|
+
const runId = argv[0] ?? "";
|
|
7865
|
+
const hi = argv.indexOf("--home");
|
|
7866
|
+
const home = hi >= 0 ? argv[hi + 1] ?? "" : "";
|
|
7867
|
+
if (!RUN_ID_RE.test(runId)) {
|
|
7868
|
+
deps.stderr.write("deskbuddy: \u542F\u52A8\u53C2\u6570\u4E0D\u5BF9\n");
|
|
7869
|
+
return 2;
|
|
7870
|
+
}
|
|
7871
|
+
if (home) {
|
|
7872
|
+
if (!/^(?:[A-Za-z]:[\\/]|\/)/.test(home)) {
|
|
7873
|
+
deps.stderr.write("deskbuddy: \u542F\u52A8\u53C2\u6570\u4E0D\u5BF9\n");
|
|
7874
|
+
return 2;
|
|
7875
|
+
}
|
|
7876
|
+
process.env.DESKBUDDY_HOME = home;
|
|
7877
|
+
deps.env.DESKBUDDY_HOME = home;
|
|
7878
|
+
}
|
|
7879
|
+
const plan = ld.claim(runId);
|
|
7880
|
+
if (!plan) {
|
|
7881
|
+
deps.stderr.write("deskbuddy: \u8FD9\u4E2A\u7A97\u53E3\u8981\u505A\u7684\u4E8B\u5DF2\u7ECF\u53D6\u6D88\u6216\u6539\u5728\u540E\u53F0\u6267\u884C\u4E86\uFF0C\u53EF\u4EE5\u5173\u6389\u8FD9\u4E2A\u7A97\u53E3\n");
|
|
7882
|
+
return 1;
|
|
7883
|
+
}
|
|
7884
|
+
if (!ld.isDir(plan.cwd)) {
|
|
7885
|
+
deps.stderr.write("deskbuddy: \u5DE5\u4F5C\u76EE\u5F55\u5DF2\u7ECF\u4E0D\u5728\u4E86\n");
|
|
7886
|
+
return 1;
|
|
7887
|
+
}
|
|
7888
|
+
ld.chdir(plan.cwd);
|
|
7889
|
+
const args = plan.resume ? ["--resume", plan.resume] : [];
|
|
7890
|
+
let real2 = null;
|
|
7891
|
+
try {
|
|
7892
|
+
real2 = deps.findReal("claude_code");
|
|
7893
|
+
} catch {
|
|
7894
|
+
real2 = null;
|
|
7895
|
+
}
|
|
7896
|
+
if (!real2) {
|
|
7897
|
+
deps.stderr.write("deskbuddy: \u627E\u4E0D\u5230 claude\n");
|
|
7898
|
+
return 127;
|
|
7899
|
+
}
|
|
7900
|
+
const d2 = { ...deps, cwd: () => plan.cwd };
|
|
7901
|
+
let r = null;
|
|
7902
|
+
try {
|
|
7903
|
+
r = await wrapInPty("claude_code", real2, args, d2, { windowId: runId, launched: true });
|
|
7904
|
+
} catch (e) {
|
|
7905
|
+
deps.log(`\u5F00\u7A97\u5305\u88C5\u5931\u8D25\uFF0C\u76F4\u63A5\u8FD0\u884C\uFF1A${e.message}`);
|
|
7906
|
+
}
|
|
7907
|
+
if (r !== null) return r;
|
|
7908
|
+
return deps.runDirect(real2, args);
|
|
7909
|
+
}
|
|
7910
|
+
async function wrapInPty(tool, real2, args, deps, opts = {}) {
|
|
6405
7911
|
const pty = deps.loadPty();
|
|
6406
7912
|
const Headless = deps.loadHeadless();
|
|
6407
7913
|
if (!pty || !Headless) {
|
|
@@ -6409,7 +7915,7 @@ async function wrapInPty(tool, real2, args, deps) {
|
|
|
6409
7915
|
return null;
|
|
6410
7916
|
}
|
|
6411
7917
|
const { stdout: stdout7 } = deps;
|
|
6412
|
-
const windowId =
|
|
7918
|
+
const windowId = opts.windowId ?? randomBytes8(16).toString("hex");
|
|
6413
7919
|
const cols = stdout7.columns || 80;
|
|
6414
7920
|
const rows = stdout7.rows || 24;
|
|
6415
7921
|
const term = new Headless({ cols, rows, allowProposedApi: true });
|
|
@@ -6426,9 +7932,9 @@ async function wrapInPty(tool, real2, args, deps) {
|
|
|
6426
7932
|
deps.log(`pty \u8D77\u4E0D\u6765\uFF0C\u653E\u884C\u76F4\u8DD1\uFF1A${e.message}`);
|
|
6427
7933
|
return null;
|
|
6428
7934
|
}
|
|
6429
|
-
return runAttached(tool, p, term, windowId, deps);
|
|
7935
|
+
return runAttached(tool, p, term, windowId, deps, opts.launched === true);
|
|
6430
7936
|
}
|
|
6431
|
-
function runAttached(tool, p, term0, windowId, deps) {
|
|
7937
|
+
function runAttached(tool, p, term0, windowId, deps, launched = false) {
|
|
6432
7938
|
const { stdin, stdout: stdout7 } = deps;
|
|
6433
7939
|
let term = term0;
|
|
6434
7940
|
let finished = false;
|
|
@@ -6458,7 +7964,8 @@ function runAttached(tool, p, term0, windowId, deps) {
|
|
|
6458
7964
|
held = null;
|
|
6459
7965
|
for (const s of h) toPty(s);
|
|
6460
7966
|
},
|
|
6461
|
-
log: (m) => deps.log(`[${windowId.slice(0, 8)}] ${m}`)
|
|
7967
|
+
log: (m) => deps.log(`[${windowId.slice(0, 8)}] ${m}`),
|
|
7968
|
+
startupIdle: launched
|
|
6462
7969
|
});
|
|
6463
7970
|
p.onData((d) => {
|
|
6464
7971
|
try {
|
|
@@ -6515,8 +8022,18 @@ function runAttached(tool, p, term0, windowId, deps) {
|
|
|
6515
8022
|
} catch (e) {
|
|
6516
8023
|
deps.log(`\u63A5\u7BA1\u7EC8\u7AEF\u51FA\u9519\uFF1A${e.message}`);
|
|
6517
8024
|
}
|
|
8025
|
+
const secret = randomBytes8(32);
|
|
6518
8026
|
try {
|
|
6519
|
-
writeWindow(windowId, {
|
|
8027
|
+
writeWindow(windowId, {
|
|
8028
|
+
pid: process.pid,
|
|
8029
|
+
childPid: p.pid,
|
|
8030
|
+
sock,
|
|
8031
|
+
tool,
|
|
8032
|
+
cwd: deps.cwd(),
|
|
8033
|
+
at: Date.now(),
|
|
8034
|
+
secret: secret.toString("hex"),
|
|
8035
|
+
...launched ? { launched: true } : {}
|
|
8036
|
+
});
|
|
6520
8037
|
} catch (e) {
|
|
6521
8038
|
deps.log(`\u767B\u8BB0\u7A97\u53E3\u5931\u8D25\uFF1A${e.message}`);
|
|
6522
8039
|
}
|
|
@@ -6524,6 +8041,7 @@ function runAttached(tool, p, term0, windowId, deps) {
|
|
|
6524
8041
|
if (channelOk) {
|
|
6525
8042
|
void startChannelServer(
|
|
6526
8043
|
sock,
|
|
8044
|
+
secret,
|
|
6527
8045
|
(req2, reply, alive) => {
|
|
6528
8046
|
try {
|
|
6529
8047
|
injector.submit({ ...req2, expireAt: req2.expireAt || Date.now() + DEFAULT_TTL_MS, reply, alive });
|