deskbuddy 0.2.0-beta.2 → 0.2.0-beta.3
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 +891 -114
- package/dist/hook.cjs +33 -1
- package/dist/wake.cjs +764 -0
- package/hook/hook.cjs +33 -1
- 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 writeFileSync14, rmSync as rmSync9 } 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 randomBytes6 } from "crypto";
|
|
410
410
|
import { existsSync as existsSync10 } from "fs";
|
|
411
411
|
import { basename as basename6 } from "path";
|
|
412
412
|
import WebSocket2 from "ws";
|
|
@@ -620,7 +620,7 @@ function atomicCopy(from, to) {
|
|
|
620
620
|
function syncHookFile(opts = {}) {
|
|
621
621
|
const target = opts.target ?? installedHookPath();
|
|
622
622
|
const version = opts.version ?? packageVersion();
|
|
623
|
-
const SIBLINGS = ["gate.cjs", "opencode.mjs"];
|
|
623
|
+
const SIBLINGS = ["gate.cjs", "opencode.mjs", "wake.cjs"];
|
|
624
624
|
const siblingMissing = () => SIBLINGS.some((name) => {
|
|
625
625
|
if (existsSync5(join5(dirname3(target), name))) return false;
|
|
626
626
|
try {
|
|
@@ -1295,7 +1295,7 @@ function buildSpawnPlan(cmd, cwd, gate, home = homedir4()) {
|
|
|
1295
1295
|
env: { DESKBUDDY_GATE_RUN: gate.runId, DESKBUDDY_NODE: gate.nodePath, OPENCODE_CONFIG_CONTENT: OPENCODE_DENY_ALL }
|
|
1296
1296
|
} : null;
|
|
1297
1297
|
default:
|
|
1298
|
-
return { file, args: claudePlan(cmd, gate), cwd };
|
|
1298
|
+
return { file, args: claudePlan(cmd, gate), cwd, env: { DESKBUDDY_NO_WAKE: "1" } };
|
|
1299
1299
|
}
|
|
1300
1300
|
}
|
|
1301
1301
|
function materialize(plan, tool, d = defaultLocateDeps()) {
|
|
@@ -2437,10 +2437,14 @@ var SessionQueue = class {
|
|
|
2437
2437
|
* rejected promise,后续所有指令都会跟着被拒 —— 一次失败让这个会话**永久**
|
|
2438
2438
|
* 收不到指令,而且没有任何报错。
|
|
2439
2439
|
*/
|
|
2440
|
-
enqueue(sessionId, job) {
|
|
2440
|
+
enqueue(sessionId, job, onError) {
|
|
2441
2441
|
const key = sessionId || "";
|
|
2442
2442
|
const prev = this.#tails.get(key) ?? Promise.resolve();
|
|
2443
|
-
const next = prev.then(job).catch(() => {
|
|
2443
|
+
const next = prev.then(job).catch((e) => {
|
|
2444
|
+
try {
|
|
2445
|
+
onError?.(e);
|
|
2446
|
+
} catch {
|
|
2447
|
+
}
|
|
2444
2448
|
});
|
|
2445
2449
|
this.#tails.set(key, next);
|
|
2446
2450
|
void next.then(() => {
|
|
@@ -3553,6 +3557,590 @@ function windowForSession(sessionId) {
|
|
|
3553
3557
|
return id;
|
|
3554
3558
|
}
|
|
3555
3559
|
|
|
3560
|
+
// src/core/wake.ts
|
|
3561
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
3562
|
+
import { createHash as createHash3, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3563
|
+
import { chmodSync as chmodSync4, closeSync, mkdirSync as mkdirSync14, openSync, readdirSync as readdirSync3, readFileSync as readFileSync13, readSync, renameSync as renameSync11, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync13 } from "fs";
|
|
3564
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3565
|
+
import { join as join17 } from "path";
|
|
3566
|
+
function wakeDir(home = deskbuddyHome()) {
|
|
3567
|
+
return join17(home, "wake");
|
|
3568
|
+
}
|
|
3569
|
+
function marksDir(home = deskbuddyHome()) {
|
|
3570
|
+
return join17(wakeDir(home), "marks");
|
|
3571
|
+
}
|
|
3572
|
+
function safeSid2(sessionId) {
|
|
3573
|
+
return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
|
|
3574
|
+
}
|
|
3575
|
+
function homeTag(home) {
|
|
3576
|
+
return createHash3("sha1").update(home).digest("hex").slice(0, 16);
|
|
3577
|
+
}
|
|
3578
|
+
function wakeChannelPath(platform = process.platform, home = deskbuddyHome()) {
|
|
3579
|
+
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-wake-${homeTag(home)}`;
|
|
3580
|
+
const primary = join17(wakeDir(home), "wake.sock");
|
|
3581
|
+
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
3582
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
3583
|
+
return join17(tmpdir2(), `deskbuddy-${uid}`, `wake-${homeTag(home)}.sock`);
|
|
3584
|
+
}
|
|
3585
|
+
function pidExists(pid) {
|
|
3586
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
3587
|
+
try {
|
|
3588
|
+
process.kill(pid, 0);
|
|
3589
|
+
return true;
|
|
3590
|
+
} catch {
|
|
3591
|
+
return false;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
function writeWakeMark(sessionId, agentPid, now = Date.now()) {
|
|
3595
|
+
const safe = safeSid2(sessionId);
|
|
3596
|
+
if (!safe) return;
|
|
3597
|
+
try {
|
|
3598
|
+
mkdirSync14(marksDir(), { recursive: true, mode: 448 });
|
|
3599
|
+
const file = join17(marksDir(), `${safe}.json`);
|
|
3600
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
3601
|
+
writeFileSync13(tmp, JSON.stringify({ agentPid: agentPid ?? 0, daemonPid: process.pid, at: now }), { encoding: "utf8", mode: 384 });
|
|
3602
|
+
renameSync11(tmp, file);
|
|
3603
|
+
} catch {
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
function removeWakeMark(sessionId) {
|
|
3607
|
+
const safe = safeSid2(sessionId);
|
|
3608
|
+
if (!safe) return;
|
|
3609
|
+
try {
|
|
3610
|
+
rmSync7(join17(marksDir(), `${safe}.json`), { force: true });
|
|
3611
|
+
} catch {
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
function clearWakeMarks() {
|
|
3615
|
+
let names = [];
|
|
3616
|
+
try {
|
|
3617
|
+
names = readdirSync3(marksDir());
|
|
3618
|
+
} catch {
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
for (const n of names) {
|
|
3622
|
+
try {
|
|
3623
|
+
rmSync7(join17(marksDir(), n), { force: true });
|
|
3624
|
+
} catch {
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
function isWakeMarked(sessionId, alive = pidExists) {
|
|
3629
|
+
const safe = safeSid2(sessionId);
|
|
3630
|
+
if (!safe) return false;
|
|
3631
|
+
try {
|
|
3632
|
+
const m = JSON.parse(readFileSync13(join17(marksDir(), `${safe}.json`), "utf8"));
|
|
3633
|
+
const daemonPid = Number(m?.daemonPid) || 0;
|
|
3634
|
+
if (!daemonPid || !alive(daemonPid)) return false;
|
|
3635
|
+
const pid = Number(m?.agentPid) || 0;
|
|
3636
|
+
return pid > 0 ? alive(pid) : true;
|
|
3637
|
+
} catch {
|
|
3638
|
+
return false;
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
function withWake(states, alive) {
|
|
3642
|
+
return states.map((s) => s.remoteWindow === true || !isWakeMarked(s.sessionId, alive) ? s : { ...s, remoteWindow: true });
|
|
3643
|
+
}
|
|
3644
|
+
function wakeSecretPath(home = deskbuddyHome()) {
|
|
3645
|
+
return join17(wakeDir(home), "secret");
|
|
3646
|
+
}
|
|
3647
|
+
function writeWakeSecret(hex, platform = process.platform) {
|
|
3648
|
+
const file = wakeSecretPath();
|
|
3649
|
+
mkdirSync14(wakeDir(), { recursive: true, mode: 448 });
|
|
3650
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
3651
|
+
writeFileSync13(tmp, hex, { encoding: "utf8", mode: 384 });
|
|
3652
|
+
if (platform === "win32") {
|
|
3653
|
+
try {
|
|
3654
|
+
execFileSync7("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
|
|
3655
|
+
} catch {
|
|
3656
|
+
renameSync11(tmp, file);
|
|
3657
|
+
return "\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u6587\u4EF6\u7684\u6743\u9650\u6CA1\u80FD\u6536\u7D27\uFF08icacls \u5931\u8D25\uFF09";
|
|
3658
|
+
}
|
|
3659
|
+
} else {
|
|
3660
|
+
chmodSync4(tmp, 384);
|
|
3661
|
+
}
|
|
3662
|
+
renameSync11(tmp, file);
|
|
3663
|
+
return "";
|
|
3664
|
+
}
|
|
3665
|
+
function currentUserSid() {
|
|
3666
|
+
try {
|
|
3667
|
+
const out = execFileSync7("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
|
|
3668
|
+
const m = /"(S-1-[0-9-]+)"/.exec(out);
|
|
3669
|
+
if (m) return `*${m[1]}`;
|
|
3670
|
+
} catch {
|
|
3671
|
+
}
|
|
3672
|
+
return String(process.env.USERNAME);
|
|
3673
|
+
}
|
|
3674
|
+
function canonicalJson(v) {
|
|
3675
|
+
if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
|
|
3676
|
+
if (v && typeof v === "object") {
|
|
3677
|
+
const o = v;
|
|
3678
|
+
return "{" + Object.keys(o).filter((k) => o[k] !== void 0).sort().map((k) => JSON.stringify(k) + ":" + canonicalJson(o[k])).join(",") + "}";
|
|
3679
|
+
}
|
|
3680
|
+
return JSON.stringify(v === void 0 ? null : v);
|
|
3681
|
+
}
|
|
3682
|
+
function wakeMac(secret, ...parts) {
|
|
3683
|
+
return createHmac2("sha256", secret).update(parts.join("|"), "utf8").digest("hex");
|
|
3684
|
+
}
|
|
3685
|
+
var MAC_RE = /^[0-9a-f]{64}$/;
|
|
3686
|
+
function macEqual(a, b) {
|
|
3687
|
+
if (typeof a !== "string" || typeof b !== "string" || !MAC_RE.test(a) || !MAC_RE.test(b)) return false;
|
|
3688
|
+
return timingSafeEqual2(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
3689
|
+
}
|
|
3690
|
+
var WAKE_MARK_RE = /\[DeskBuddy#([0-9A-Za-z_-]{1,64})(?::[0-9a-f]{32})?\]/;
|
|
3691
|
+
var INTERRUPT_RE = /^\[Request interrupted by user/;
|
|
3692
|
+
var TRANSCRIPT_TAIL = 256 * 1024;
|
|
3693
|
+
function userText(content) {
|
|
3694
|
+
if (typeof content === "string") return content;
|
|
3695
|
+
if (!Array.isArray(content)) return "";
|
|
3696
|
+
return content.filter((x) => x && x.type === "text").map((x) => String(x.text ?? "")).join("\n");
|
|
3697
|
+
}
|
|
3698
|
+
function analyzeTranscript(file) {
|
|
3699
|
+
if (!file) return null;
|
|
3700
|
+
let fd;
|
|
3701
|
+
try {
|
|
3702
|
+
const size = statSync4(file).size;
|
|
3703
|
+
const len = Math.min(size, TRANSCRIPT_TAIL);
|
|
3704
|
+
const buf = Buffer.alloc(len);
|
|
3705
|
+
fd = openSync(file, "r");
|
|
3706
|
+
readSync(fd, buf, 0, len, size - len);
|
|
3707
|
+
const lines2 = buf.toString("utf8").split("\n");
|
|
3708
|
+
let seg = false;
|
|
3709
|
+
let reply = null;
|
|
3710
|
+
let humanAfter = false;
|
|
3711
|
+
let interrupted = null;
|
|
3712
|
+
for (let i = lines2.length - 1; i >= 0; i--) {
|
|
3713
|
+
const line = lines2[i];
|
|
3714
|
+
if (line.indexOf('"assistant"') === -1 && line.indexOf('"user"') === -1) continue;
|
|
3715
|
+
let o;
|
|
3716
|
+
try {
|
|
3717
|
+
o = JSON.parse(line);
|
|
3718
|
+
} catch {
|
|
3719
|
+
continue;
|
|
3720
|
+
}
|
|
3721
|
+
if (!o || !o.message) continue;
|
|
3722
|
+
if (o.type === "assistant") {
|
|
3723
|
+
const t = userText(o.message.content).trim();
|
|
3724
|
+
if (t) {
|
|
3725
|
+
seg = true;
|
|
3726
|
+
if (reply === null) reply = t;
|
|
3727
|
+
}
|
|
3728
|
+
continue;
|
|
3729
|
+
}
|
|
3730
|
+
if (o.type !== "user") continue;
|
|
3731
|
+
const text = userText(o.message.content);
|
|
3732
|
+
if (!text) continue;
|
|
3733
|
+
const m = WAKE_MARK_RE.exec(text);
|
|
3734
|
+
if (!m && (o.isMeta === true || text.trim().startsWith("<"))) continue;
|
|
3735
|
+
if (m) return { id: m[1], answered: seg, humanAfter, interrupted: interrupted === true, reply: reply ?? "" };
|
|
3736
|
+
if (interrupted === null) interrupted = INTERRUPT_RE.test(text.trim());
|
|
3737
|
+
humanAfter = true;
|
|
3738
|
+
seg = false;
|
|
3739
|
+
reply = null;
|
|
3740
|
+
}
|
|
3741
|
+
return interrupted === null ? null : { id: "", answered: false, humanAfter, interrupted, reply: "" };
|
|
3742
|
+
} catch {
|
|
3743
|
+
return null;
|
|
3744
|
+
} finally {
|
|
3745
|
+
if (fd !== void 0) {
|
|
3746
|
+
try {
|
|
3747
|
+
closeSync(fd);
|
|
3748
|
+
} catch {
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
// src/daemon/wake.ts
|
|
3755
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
3756
|
+
import { rmSync as rmSync8 } from "fs";
|
|
3757
|
+
import { createServer as createServer3 } from "net";
|
|
3758
|
+
var BUSY_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
|
|
3759
|
+
var STOP_MIN_MS = 1500;
|
|
3760
|
+
var WAKE_MAX_QUEUE_MS = 30 * 60 * 1e3;
|
|
3761
|
+
var WATCH_STALE_MS = 30 * 60 * 1e3;
|
|
3762
|
+
var INTERRUPTED_REASON = "exec_failed";
|
|
3763
|
+
var WakeHub = class {
|
|
3764
|
+
#slots = /* @__PURE__ */ new Map();
|
|
3765
|
+
#d;
|
|
3766
|
+
constructor(deps) {
|
|
3767
|
+
this.#d = {
|
|
3768
|
+
ackTimeoutMs: 3e3,
|
|
3769
|
+
maxQueueMs: WAKE_MAX_QUEUE_MS,
|
|
3770
|
+
flightLimitMs: EXEC_TIMEOUT_MS,
|
|
3771
|
+
watchStaleMs: WATCH_STALE_MS,
|
|
3772
|
+
analyze: analyzeTranscript,
|
|
3773
|
+
...deps
|
|
3774
|
+
};
|
|
3775
|
+
}
|
|
3776
|
+
/** 这个会话有没有登记中的等待者(或刚唤醒、正在跑的那一轮) */
|
|
3777
|
+
has(sessionId) {
|
|
3778
|
+
const s = this.#slots.get(sessionId);
|
|
3779
|
+
return !!s && (!!s.reg || !!s.flight);
|
|
3780
|
+
}
|
|
3781
|
+
/** 交给唤醒路由。返回 false = 这个会话没有等待者,调用方走别的路 */
|
|
3782
|
+
submit(cmd, sessionId) {
|
|
3783
|
+
if (cmd.tool !== "claude_code") return false;
|
|
3784
|
+
const slot = this.#slots.get(sessionId);
|
|
3785
|
+
if (!slot || !slot.reg && !slot.flight) return false;
|
|
3786
|
+
slot.queue.push({ cmd, sessionId, queuedAt: this.#d.now() });
|
|
3787
|
+
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`);
|
|
3788
|
+
this.#settle(sessionId, slot);
|
|
3789
|
+
return true;
|
|
3790
|
+
}
|
|
3791
|
+
onHello(peer, h) {
|
|
3792
|
+
const sid = h.sessionId;
|
|
3793
|
+
if (!isValidId(sid)) {
|
|
3794
|
+
peer.close();
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
const slot = this.#slot(sid);
|
|
3798
|
+
if (h.transcript) slot.transcript = h.transcript;
|
|
3799
|
+
if (h.event === "Stop" && !h.re) {
|
|
3800
|
+
slot.lastStopAt = this.#d.now();
|
|
3801
|
+
const f = slot.flight;
|
|
3802
|
+
if (f) this.#onStop(sid, slot, f, h);
|
|
3803
|
+
}
|
|
3804
|
+
if (h.agentPid) {
|
|
3805
|
+
for (const [other, s] of this.#slots) {
|
|
3806
|
+
if (other === sid || !s.reg || s.agentPid !== h.agentPid || s.reg.pending) continue;
|
|
3807
|
+
this.#evict(s);
|
|
3808
|
+
for (const j of s.queue.splice(0)) {
|
|
3809
|
+
j.sessionId = sid;
|
|
3810
|
+
slot.queue.push(j);
|
|
3811
|
+
}
|
|
3812
|
+
this.#settle(other, s);
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
const old = slot.reg;
|
|
3816
|
+
if (old && old.peer !== peer) {
|
|
3817
|
+
if (old.pending || h.re) {
|
|
3818
|
+
peer.send({ t: "dup" });
|
|
3819
|
+
peer.close();
|
|
3820
|
+
this.#settle(sid, slot);
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
this.#evict(slot);
|
|
3824
|
+
}
|
|
3825
|
+
slot.reg = { peer, hello: h };
|
|
3826
|
+
if (h.agentPid) slot.agentPid = h.agentPid;
|
|
3827
|
+
peer.send({ t: "ok" });
|
|
3828
|
+
this.#settle(sid, slot);
|
|
3829
|
+
}
|
|
3830
|
+
/**
|
|
3831
|
+
* 🔴 必须确认是被唤醒的那一轮结束了:一轮结束时 hook.cjs 先写 done,tick 可能抢在这一轮的 Stop 等待者登记前
|
|
3832
|
+
* 把排队指令发给上一个等待者,紧接着登记的 Stop 带的是上一轮的回复(2026-09-30 e2e 实测)
|
|
3833
|
+
*/
|
|
3834
|
+
#onStop(sid, slot, f, h) {
|
|
3835
|
+
const id = f.job.cmd.instructionId;
|
|
3836
|
+
const t = h.tail;
|
|
3837
|
+
if (!t) {
|
|
3838
|
+
if ((h.stopAt ?? this.#d.now()) - f.firedAt < STOP_MIN_MS) {
|
|
3839
|
+
this.#d.log(`\u6307\u4EE4 ${id} \u5DF2\u5524\u9192\uFF0C\u8FD9\u6B21 Stop \u591A\u534A\u662F\u4E0A\u4E00\u8F6E\u7684\uFF0C\u7EE7\u7EED\u7B49`);
|
|
3840
|
+
return;
|
|
3841
|
+
}
|
|
3842
|
+
} else if (t.id !== id || !t.answered && !t.humanAfter) {
|
|
3843
|
+
this.#d.log(`\u6307\u4EE4 ${id} \u5DF2\u5524\u9192\uFF0C\u8FD9\u6B21 Stop \u662F\u4E0A\u4E00\u8F6E\u7684\uFF0C\u7EE7\u7EED\u7B49`);
|
|
3844
|
+
return;
|
|
3845
|
+
} else if (t.humanAfter) {
|
|
3846
|
+
this.#interrupted(sid, slot, f, "Stop \u663E\u793A\u5524\u9192\u4E4B\u540E\u53C8\u6709\u4EBA\u7C7B\u6D88\u606F");
|
|
3847
|
+
return;
|
|
3848
|
+
}
|
|
3849
|
+
slot.flight = void 0;
|
|
3850
|
+
this.#d.log(`\u6307\u4EE4 ${id} \u7A97\u53E3\u8FD9\u4E00\u8F6E\u5DF2\u7ED3\u675F\uFF0C\u56DE\u590D ${(h.last ?? "").length} \u5B57`);
|
|
3851
|
+
this.#d.done(f.job.cmd, sid, h.last ?? "");
|
|
3852
|
+
}
|
|
3853
|
+
#interrupted(sid, slot, f, why) {
|
|
3854
|
+
slot.flight = void 0;
|
|
3855
|
+
this.#d.log(`\u6307\u4EE4 ${f.job.cmd.instructionId} \u88AB\u5524\u9192\u7684\u90A3\u4E00\u8F6E\u88AB\u6253\u65AD\uFF08${why}\uFF09`);
|
|
3856
|
+
this.#d.report(f.job.cmd, sid, "failed", INTERRUPTED_REASON);
|
|
3857
|
+
}
|
|
3858
|
+
onMessage(peer, m) {
|
|
3859
|
+
const found = this.#find(peer);
|
|
3860
|
+
if (!found) return;
|
|
3861
|
+
const [sid, slot, reg] = found;
|
|
3862
|
+
const p = reg.pending;
|
|
3863
|
+
if (!p || m.id !== p.job.cmd.instructionId) return;
|
|
3864
|
+
clearTimeout(p.timer);
|
|
3865
|
+
reg.pending = void 0;
|
|
3866
|
+
if (m.t === "fired") {
|
|
3867
|
+
const now = this.#d.now();
|
|
3868
|
+
slot.flight = { job: p.job, firedAt: now, paused: 0, lastTick: now };
|
|
3869
|
+
slot.reg = void 0;
|
|
3870
|
+
this.#d.log(`\u6307\u4EE4 ${p.job.cmd.instructionId} \u5DF2\u5524\u9192\u7A97\u53E3 len=${p.job.cmd.text.length}`);
|
|
3871
|
+
this.#d.report(p.job.cmd, sid, "started");
|
|
3872
|
+
} else {
|
|
3873
|
+
slot.reg = void 0;
|
|
3874
|
+
this.#d.log(`\u6307\u4EE4 ${p.job.cmd.instructionId} \u7B49\u5F85\u8005\u8BF4\u7A97\u53E3\u5DF2\u4E0D\u5728\uFF0C\u6539\u8D70\u540E\u53F0`);
|
|
3875
|
+
this.#d.fallback(p.job.cmd);
|
|
3876
|
+
}
|
|
3877
|
+
this.#settle(sid, slot);
|
|
3878
|
+
}
|
|
3879
|
+
onClose(peer) {
|
|
3880
|
+
const found = this.#find(peer);
|
|
3881
|
+
if (!found) return;
|
|
3882
|
+
const [sid, slot, reg] = found;
|
|
3883
|
+
if (reg.pending) {
|
|
3884
|
+
const job = reg.pending.job;
|
|
3885
|
+
clearTimeout(reg.pending.timer);
|
|
3886
|
+
reg.pending = void 0;
|
|
3887
|
+
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`);
|
|
3888
|
+
this.#d.report(job.cmd, sid, "failed", "window_failed");
|
|
3889
|
+
}
|
|
3890
|
+
slot.reg = void 0;
|
|
3891
|
+
this.#settle(sid, slot);
|
|
3892
|
+
}
|
|
3893
|
+
/** 每秒一次:过期、窗口关闭、打断、等结果超时、忙转闲 */
|
|
3894
|
+
tick() {
|
|
3895
|
+
const now = this.#d.now();
|
|
3896
|
+
for (const [sid, slot] of [...this.#slots]) {
|
|
3897
|
+
if (slot.agentPid && !this.#d.alive(slot.agentPid)) {
|
|
3898
|
+
if (slot.reg) this.#evict(slot, sid);
|
|
3899
|
+
slot.flight = void 0;
|
|
3900
|
+
slot.agentPid = void 0;
|
|
3901
|
+
this.#drain(slot, "\u6392\u961F\u65F6\u7A97\u53E3\u5173\u4E86");
|
|
3902
|
+
}
|
|
3903
|
+
const f = slot.flight;
|
|
3904
|
+
if (f) {
|
|
3905
|
+
const t = slot.transcript ? this.#d.analyze(slot.transcript) : null;
|
|
3906
|
+
if (t && t.id === f.job.cmd.instructionId && t.interrupted) {
|
|
3907
|
+
this.#interrupted(sid, slot, f, "Esc");
|
|
3908
|
+
} else {
|
|
3909
|
+
const st = this.#d.readState(sid);
|
|
3910
|
+
if (st?.state === "ask_approval") f.paused += now - f.lastTick;
|
|
3911
|
+
f.lastTick = now;
|
|
3912
|
+
if (now - f.firedAt - f.paused > this.#d.flightLimitMs) {
|
|
3913
|
+
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`);
|
|
3914
|
+
slot.flight = void 0;
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3918
|
+
this.#settle(sid, slot);
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
logError(msg) {
|
|
3922
|
+
this.#d.log(msg);
|
|
3923
|
+
}
|
|
3924
|
+
/** 关掉全部连接(daemon 退出) */
|
|
3925
|
+
closeAll() {
|
|
3926
|
+
for (const slot of this.#slots.values()) {
|
|
3927
|
+
if (slot.reg?.pending) clearTimeout(slot.reg.pending.timer);
|
|
3928
|
+
slot.reg?.peer.close();
|
|
3929
|
+
}
|
|
3930
|
+
this.#slots.clear();
|
|
3931
|
+
}
|
|
3932
|
+
#slot(sid) {
|
|
3933
|
+
let s = this.#slots.get(sid);
|
|
3934
|
+
if (!s) {
|
|
3935
|
+
s = { queue: [], lastStopAt: 0, marked: false };
|
|
3936
|
+
this.#slots.set(sid, s);
|
|
3937
|
+
}
|
|
3938
|
+
return s;
|
|
3939
|
+
}
|
|
3940
|
+
#find(peer) {
|
|
3941
|
+
for (const [sid, s] of this.#slots) if (s.reg && s.reg.peer === peer) return [sid, s, s.reg];
|
|
3942
|
+
return null;
|
|
3943
|
+
}
|
|
3944
|
+
#evict(slot, sid) {
|
|
3945
|
+
const r = slot.reg;
|
|
3946
|
+
if (!r) return;
|
|
3947
|
+
slot.reg = void 0;
|
|
3948
|
+
if (r.pending) {
|
|
3949
|
+
clearTimeout(r.pending.timer);
|
|
3950
|
+
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`);
|
|
3951
|
+
this.#d.report(r.pending.job.cmd, sid ?? r.pending.job.sessionId, "failed", "window_failed");
|
|
3952
|
+
r.pending = void 0;
|
|
3953
|
+
}
|
|
3954
|
+
r.peer.send({ t: "bye" });
|
|
3955
|
+
r.peer.close();
|
|
3956
|
+
}
|
|
3957
|
+
/** 没碰过窗口的排队指令按原顺序交回后台 */
|
|
3958
|
+
#drain(slot, why) {
|
|
3959
|
+
for (const j of slot.queue.splice(0)) {
|
|
3960
|
+
this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} ${why}\uFF0C\u6539\u8D70\u540E\u53F0`);
|
|
3961
|
+
this.#d.fallback(j.cmd);
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3964
|
+
#busy(sid, slot) {
|
|
3965
|
+
if (slot.flight || slot.reg?.pending) return true;
|
|
3966
|
+
const st = this.#d.readState(sid);
|
|
3967
|
+
if (!st || !BUSY_STATES.has(st.state) || st.updatedAt <= slot.lastStopAt) return false;
|
|
3968
|
+
if (slot.transcript && this.#d.analyze(slot.transcript)?.interrupted) return false;
|
|
3969
|
+
return this.#d.now() - st.updatedAt <= this.#d.watchStaleMs;
|
|
3970
|
+
}
|
|
3971
|
+
#expired(job, now) {
|
|
3972
|
+
return job.cmd.expireAt > 0 ? isExpired(job.cmd, now) : now - job.queuedAt > this.#d.maxQueueMs;
|
|
3973
|
+
}
|
|
3974
|
+
/** 排队 / 发出 / 过期 */
|
|
3975
|
+
#pump(sid, slot) {
|
|
3976
|
+
const now = this.#d.now();
|
|
3977
|
+
for (const j of [...slot.queue]) {
|
|
3978
|
+
if (!this.#expired(j, now)) continue;
|
|
3979
|
+
slot.queue.splice(slot.queue.indexOf(j), 1);
|
|
3980
|
+
this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} \u6392\u961F\u5230\u8FC7\u671F\u4E5F\u6CA1\u7B49\u5230\u7A97\u53E3\u7A7A\u95F2`);
|
|
3981
|
+
this.#d.report(j.cmd, sid, "rejected", "expired");
|
|
3982
|
+
}
|
|
3983
|
+
if (slot.queue.length === 0) return;
|
|
3984
|
+
if (!slot.reg || this.#busy(sid, slot)) {
|
|
3985
|
+
for (const j of slot.queue) {
|
|
3986
|
+
if (j.queuedSent) continue;
|
|
3987
|
+
j.queuedSent = true;
|
|
3988
|
+
this.#d.log(`\u6307\u4EE4 ${j.cmd.instructionId} ${slot.reg ? "\u7A97\u53E3\u6B63\u5FD9" : "\u6682\u65E0\u7B49\u5F85\u8005"}\uFF0C\u6392\u961F`);
|
|
3989
|
+
this.#d.report(j.cmd, sid, "queued");
|
|
3990
|
+
}
|
|
3991
|
+
return;
|
|
3992
|
+
}
|
|
3993
|
+
const job = slot.queue.shift();
|
|
3994
|
+
const reg = slot.reg;
|
|
3995
|
+
reg.pending = {
|
|
3996
|
+
job,
|
|
3997
|
+
timer: setTimeout(() => {
|
|
3998
|
+
if (reg.pending?.job === job) reg.peer.close();
|
|
3999
|
+
}, this.#d.ackTimeoutMs)
|
|
4000
|
+
};
|
|
4001
|
+
reg.pending.timer.unref?.();
|
|
4002
|
+
reg.peer.send({ t: "fire", id: job.cmd.instructionId, text: job.cmd.text });
|
|
4003
|
+
}
|
|
4004
|
+
/** 等待者没了、又没有在跑的唤醒轮:排队的交回后台;然后排队 / 发出;同步标记;清空闲槽 */
|
|
4005
|
+
#settle(sid, slot) {
|
|
4006
|
+
if (!slot.reg && !slot.flight && slot.queue.length > 0) this.#drain(slot, "\u7B49\u5F85\u8005\u6CA1\u4E86");
|
|
4007
|
+
this.#pump(sid, slot);
|
|
4008
|
+
const on = !!slot.reg || !!slot.flight;
|
|
4009
|
+
if (on !== slot.marked) {
|
|
4010
|
+
slot.marked = on;
|
|
4011
|
+
this.#d.mark(sid, slot.agentPid, on);
|
|
4012
|
+
}
|
|
4013
|
+
if (!on && slot.queue.length === 0 && !slot.reg) this.#slots.delete(sid);
|
|
4014
|
+
}
|
|
4015
|
+
};
|
|
4016
|
+
function parseTail(v) {
|
|
4017
|
+
if (!v || typeof v !== "object") return void 0;
|
|
4018
|
+
const t = v;
|
|
4019
|
+
if (typeof t.id !== "string" || t.id !== "" && !isValidId(t.id)) return void 0;
|
|
4020
|
+
return { id: t.id, answered: t.answered === true, humanAfter: t.humanAfter === true, interrupted: t.interrupted === true };
|
|
4021
|
+
}
|
|
4022
|
+
function parseHello(m) {
|
|
4023
|
+
if (m.t !== "hello" || typeof m.sessionId !== "string") return null;
|
|
4024
|
+
const num = (v) => typeof v === "number" && Number.isInteger(v) && v > 0 ? v : void 0;
|
|
4025
|
+
return {
|
|
4026
|
+
sessionId: m.sessionId,
|
|
4027
|
+
agentPid: num(m.agentPid),
|
|
4028
|
+
waiterPid: num(m.waiterPid),
|
|
4029
|
+
event: typeof m.event === "string" ? m.event : void 0,
|
|
4030
|
+
last: typeof m.last === "string" ? m.last : void 0,
|
|
4031
|
+
tail: parseTail(m.tail),
|
|
4032
|
+
transcript: typeof m.transcript === "string" && m.transcript.endsWith(".jsonl") ? m.transcript : void 0,
|
|
4033
|
+
stopAt: num(m.stopAt),
|
|
4034
|
+
re: m.re === true
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
4037
|
+
var NONCE_RE = /^[0-9a-f]{32}$/;
|
|
4038
|
+
var AUTH_TIMEOUT_MS = 5e3;
|
|
4039
|
+
function startWakeServer(path, hub, secret, platform = process.platform) {
|
|
4040
|
+
if (platform !== "win32") {
|
|
4041
|
+
if (!ensureChannelDir(path)) return Promise.resolve(null);
|
|
4042
|
+
try {
|
|
4043
|
+
rmSync8(path, { force: true });
|
|
4044
|
+
} catch {
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
return new Promise((resolve5) => {
|
|
4048
|
+
const server = createServer3((sock) => {
|
|
4049
|
+
sock.on("error", () => {
|
|
4050
|
+
});
|
|
4051
|
+
let phase = "hello";
|
|
4052
|
+
let nonceW = "";
|
|
4053
|
+
let nonceD = "";
|
|
4054
|
+
let regSid = "";
|
|
4055
|
+
const authTimer = setTimeout(() => {
|
|
4056
|
+
if (phase !== "ready") sock.destroy();
|
|
4057
|
+
}, AUTH_TIMEOUT_MS);
|
|
4058
|
+
authTimer.unref();
|
|
4059
|
+
const raw = (msg) => {
|
|
4060
|
+
if (sock.destroyed || !sock.writable) return;
|
|
4061
|
+
try {
|
|
4062
|
+
sock.write(JSON.stringify(msg) + "\n");
|
|
4063
|
+
} catch {
|
|
4064
|
+
}
|
|
4065
|
+
};
|
|
4066
|
+
const peer = {
|
|
4067
|
+
send(msg) {
|
|
4068
|
+
if (msg.t === "fire") raw({ ...msg, sid: regSid, mac: wakeMac(secret, nonceW, regSid, String(msg.id), String(msg.text)) });
|
|
4069
|
+
else raw(msg);
|
|
4070
|
+
},
|
|
4071
|
+
close() {
|
|
4072
|
+
sock.end();
|
|
4073
|
+
setTimeout(() => sock.destroy(), 200).unref();
|
|
4074
|
+
}
|
|
4075
|
+
};
|
|
4076
|
+
sock.on("close", () => {
|
|
4077
|
+
clearTimeout(authTimer);
|
|
4078
|
+
if (phase === "ready") {
|
|
4079
|
+
try {
|
|
4080
|
+
hub.onClose(peer);
|
|
4081
|
+
} catch (e) {
|
|
4082
|
+
hub.logError(`\u7B49\u5F85\u8005\u65AD\u5F00\u65F6\u5904\u7406\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
});
|
|
4086
|
+
const onLine = (line) => {
|
|
4087
|
+
const m = JSON.parse(line);
|
|
4088
|
+
if (!m || typeof m !== "object") throw new Error("bad frame");
|
|
4089
|
+
if (phase === "hello") {
|
|
4090
|
+
if (m.t !== "hello" || typeof m.nonceW !== "string" || !NONCE_RE.test(m.nonceW)) {
|
|
4091
|
+
sock.destroy();
|
|
4092
|
+
return;
|
|
4093
|
+
}
|
|
4094
|
+
nonceW = m.nonceW;
|
|
4095
|
+
nonceD = randomBytes5(16).toString("hex");
|
|
4096
|
+
phase = "auth2";
|
|
4097
|
+
raw({ t: "auth", nonceD, mac: wakeMac(secret, "d", nonceW, nonceD) });
|
|
4098
|
+
return;
|
|
4099
|
+
}
|
|
4100
|
+
if (phase === "auth2") {
|
|
4101
|
+
const reg = m.reg && typeof m.reg === "object" ? m.reg : null;
|
|
4102
|
+
const hello = reg ? parseHello({ ...reg, t: "hello" }) : null;
|
|
4103
|
+
if (m.t !== "auth2" || !hello || !macEqual(m.mac, wakeMac(secret, "w", nonceD, canonicalJson(reg)))) {
|
|
4104
|
+
sock.destroy();
|
|
4105
|
+
return;
|
|
4106
|
+
}
|
|
4107
|
+
regSid = hello.sessionId;
|
|
4108
|
+
phase = "ready";
|
|
4109
|
+
clearTimeout(authTimer);
|
|
4110
|
+
hub.onHello(peer, hello);
|
|
4111
|
+
return;
|
|
4112
|
+
}
|
|
4113
|
+
if (m.t !== "fired" && m.t !== "refuse" || typeof m.id !== "string" || !macEqual(m.mac, wakeMac(secret, "u", nonceD, m.t, m.id))) {
|
|
4114
|
+
sock.destroy();
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
hub.onMessage(peer, m);
|
|
4118
|
+
};
|
|
4119
|
+
lines(sock, (line) => {
|
|
4120
|
+
try {
|
|
4121
|
+
onLine(line);
|
|
4122
|
+
} catch {
|
|
4123
|
+
sock.destroy();
|
|
4124
|
+
}
|
|
4125
|
+
});
|
|
4126
|
+
});
|
|
4127
|
+
const oldMask = platform === "win32" ? null : process.umask(127);
|
|
4128
|
+
const restore = () => {
|
|
4129
|
+
if (oldMask !== null) process.umask(oldMask);
|
|
4130
|
+
};
|
|
4131
|
+
server.once("error", () => {
|
|
4132
|
+
restore();
|
|
4133
|
+
resolve5(null);
|
|
4134
|
+
});
|
|
4135
|
+
server.listen(path, () => {
|
|
4136
|
+
restore();
|
|
4137
|
+
server.on("error", () => {
|
|
4138
|
+
});
|
|
4139
|
+
resolve5(server);
|
|
4140
|
+
});
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4143
|
+
|
|
3556
4144
|
// src/daemon/index.ts
|
|
3557
4145
|
var IDLE_TIMEOUT_MS = 150 * 1e3;
|
|
3558
4146
|
var POLICY_VIOLATION = 1008;
|
|
@@ -3587,6 +4175,10 @@ var Daemon = class {
|
|
|
3587
4175
|
#windowScan = null;
|
|
3588
4176
|
#aliveReport = null;
|
|
3589
4177
|
#codexTimer = null;
|
|
4178
|
+
#wake;
|
|
4179
|
+
#wakeServer = null;
|
|
4180
|
+
#wakeTimer = null;
|
|
4181
|
+
#wakeReport = null;
|
|
3590
4182
|
/** url → 连着它的 CodexWatch。插入顺序 = 发现顺序(DeskBuddy 管的在前) */
|
|
3591
4183
|
#codex = /* @__PURE__ */ new Map();
|
|
3592
4184
|
#codexStarted = false;
|
|
@@ -3607,8 +4199,26 @@ var Daemon = class {
|
|
|
3607
4199
|
codexRoute: deps.codexRoute ?? ((tid) => this.#codexRouteFor(tid)),
|
|
3608
4200
|
report: deps.report ?? ((p) => api.commandResult(p)),
|
|
3609
4201
|
windowFor: deps.windowFor ?? windowForSession,
|
|
3610
|
-
sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), req2, onReply))
|
|
4202
|
+
sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), req2, onReply)),
|
|
4203
|
+
wakePath: deps.wakePath === void 0 ? wakeChannelPath() : deps.wakePath,
|
|
4204
|
+
onWindowDone: deps.onWindowDone ?? (() => {
|
|
4205
|
+
})
|
|
3611
4206
|
};
|
|
4207
|
+
this.#wake = new WakeHub({
|
|
4208
|
+
now: this.#deps.now,
|
|
4209
|
+
readState,
|
|
4210
|
+
alive: (pid) => isAlive(pid),
|
|
4211
|
+
report: (cmd, sid, status, reason) => this.#report(cmd, sid, status, "window", reason ?? ""),
|
|
4212
|
+
fallback: (cmd) => this.#dispatch(cmd),
|
|
4213
|
+
done: (cmd, sid, text) => this.#deps.onWindowDone({ instructionId: cmd.instructionId, sessionId: sid, tool: cmd.tool, text }),
|
|
4214
|
+
mark: (sid, pid, on) => {
|
|
4215
|
+
if (on) writeWakeMark(sid, pid, this.#deps.now());
|
|
4216
|
+
else removeWakeMark(sid);
|
|
4217
|
+
log(`\u4F1A\u8BDD ${sid} ${on ? "\u53EF\u4EE5" : "\u4E0D\u518D\u80FD"}\u88AB\u8BBE\u5907\u6307\u4EE4\u5524\u9192`);
|
|
4218
|
+
this.#scheduleWakeReport();
|
|
4219
|
+
},
|
|
4220
|
+
log
|
|
4221
|
+
});
|
|
3612
4222
|
this.#store.prune(this.#deps.now());
|
|
3613
4223
|
this.#coord = new ApprovalCoordinator({
|
|
3614
4224
|
store: this.#store,
|
|
@@ -3632,8 +4242,55 @@ var Daemon = class {
|
|
|
3632
4242
|
this.#codexTick();
|
|
3633
4243
|
this.#codexTimer = setInterval(() => this.#codexTick(), CODEX_WATCH_MS);
|
|
3634
4244
|
this.#codexTimer.unref();
|
|
4245
|
+
await this.startWake();
|
|
3635
4246
|
return port;
|
|
3636
4247
|
}
|
|
4248
|
+
/**
|
|
4249
|
+
* 起窗口唤醒通道(Claude Code asyncRewake 等待者连这里,见 hook/wake.cjs 与 ./wake.ts)。
|
|
4250
|
+
* 先清上次残留的标记:还活着的等待者几秒内会重连重新登记
|
|
4251
|
+
*/
|
|
4252
|
+
async startWake() {
|
|
4253
|
+
const path = this.#deps.wakePath;
|
|
4254
|
+
if (!path) return;
|
|
4255
|
+
clearWakeMarks();
|
|
4256
|
+
const secret = randomBytes6(32);
|
|
4257
|
+
let warn = "";
|
|
4258
|
+
try {
|
|
4259
|
+
warn = writeWakeSecret(secret.toString("hex"));
|
|
4260
|
+
} catch (e) {
|
|
4261
|
+
log(`\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u5199\u4E0D\u8FDB\u53BB\uFF0C\u4E0D\u8D77\u5524\u9192\u901A\u9053\uFF1A${e.message}`);
|
|
4262
|
+
return;
|
|
4263
|
+
}
|
|
4264
|
+
if (warn) log(`\u26A0\uFE0F \u5B89\u5168\u544A\u8B66\uFF1A${warn}`);
|
|
4265
|
+
this.#wakeServer = await startWakeServer(path, this.#wake, secret);
|
|
4266
|
+
if (this.#wakeServer) log("\u7A97\u53E3\u5524\u9192\u901A\u9053\u5DF2\u5C31\u7EEA");
|
|
4267
|
+
else if (process.platform === "win32")
|
|
4268
|
+
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");
|
|
4269
|
+
else log("\u7A97\u53E3\u5524\u9192\u901A\u9053\u6CA1\u8D77\u6765\uFF08\u4E0D\u5F71\u54CD\u5176\u5B83\u529F\u80FD\uFF09");
|
|
4270
|
+
this.#wakeTimer = setInterval(() => this.#wake.tick(), 1e3);
|
|
4271
|
+
this.#wakeTimer.unref();
|
|
4272
|
+
}
|
|
4273
|
+
/** 唤醒能力变了:300ms 内合并,报一次 Claude Code 的快照(remoteWindow 跟着变) */
|
|
4274
|
+
#scheduleWakeReport() {
|
|
4275
|
+
if (this.#wakeReport) return;
|
|
4276
|
+
this.#wakeReport = setTimeout(() => {
|
|
4277
|
+
this.#wakeReport = null;
|
|
4278
|
+
void this.#reportTool("claude_code");
|
|
4279
|
+
}, 300);
|
|
4280
|
+
this.#wakeReport.unref();
|
|
4281
|
+
}
|
|
4282
|
+
async #reportTool(tool) {
|
|
4283
|
+
const cfg = loadConfig();
|
|
4284
|
+
if (!cfg.clientId || !cfg.boundAt || !cfg.enabledTools.includes(tool)) return;
|
|
4285
|
+
try {
|
|
4286
|
+
await postReport(buildSnapshot(withWake(readAliveStates(tool)), tool, cfg, this.#deps.now()));
|
|
4287
|
+
} catch (e) {
|
|
4288
|
+
log(`\u4E0A\u62A5 ${tool} \u4F1A\u8BDD\u5931\u8D25\uFF1A${e.message}`);
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
get wake() {
|
|
4292
|
+
return this.#wake;
|
|
4293
|
+
}
|
|
3637
4294
|
/** 找 app-server:新出现的连上,不在了且已断开的丢掉;连着的各自 tick */
|
|
3638
4295
|
#codexTick() {
|
|
3639
4296
|
let urls = [];
|
|
@@ -3728,7 +4385,9 @@ var Daemon = class {
|
|
|
3728
4385
|
const cfg = loadConfig();
|
|
3729
4386
|
if (!cfg.clientId || !cfg.boundAt) return;
|
|
3730
4387
|
for (const tool of cfg.enabledTools) {
|
|
3731
|
-
await postReport(
|
|
4388
|
+
await postReport(
|
|
4389
|
+
buildSnapshot(withWake(readAliveStates(tool)), tool, cfg, this.#deps.now(), tool === "codex" ? this.#codexLoaded() : null)
|
|
4390
|
+
);
|
|
3732
4391
|
}
|
|
3733
4392
|
if (++this.#dirsTick % 10 === 0) await this.#reportDirs();
|
|
3734
4393
|
}
|
|
@@ -3741,6 +4400,10 @@ var Daemon = class {
|
|
|
3741
4400
|
if (this.#windowScan) clearInterval(this.#windowScan);
|
|
3742
4401
|
if (this.#aliveReport) clearInterval(this.#aliveReport);
|
|
3743
4402
|
if (this.#codexTimer) clearInterval(this.#codexTimer);
|
|
4403
|
+
if (this.#wakeTimer) clearInterval(this.#wakeTimer);
|
|
4404
|
+
if (this.#wakeReport) clearTimeout(this.#wakeReport);
|
|
4405
|
+
this.#wake.closeAll();
|
|
4406
|
+
this.#wakeServer?.close();
|
|
3744
4407
|
if (this.#codexReportTimer) clearTimeout(this.#codexReportTimer);
|
|
3745
4408
|
for (const w of this.#codex.values()) w.stop();
|
|
3746
4409
|
this.#codex.clear();
|
|
@@ -3794,7 +4457,11 @@ var Daemon = class {
|
|
|
3794
4457
|
});
|
|
3795
4458
|
ws.on("message", (raw) => {
|
|
3796
4459
|
this.#armIdle();
|
|
3797
|
-
|
|
4460
|
+
try {
|
|
4461
|
+
this.#onMessage(String(raw));
|
|
4462
|
+
} catch (e) {
|
|
4463
|
+
log(`\u5904\u7406\u4E0B\u884C\u5E27\u51FA\u9519\uFF1A${e.stack ?? e.message}`);
|
|
4464
|
+
}
|
|
3798
4465
|
});
|
|
3799
4466
|
ws.on("error", (e) => log(`\u8FDE\u63A5\u51FA\u9519: ${e.message}`));
|
|
3800
4467
|
ws.on("close", (code, reason) => {
|
|
@@ -3878,6 +4545,17 @@ var Daemon = class {
|
|
|
3878
4545
|
}
|
|
3879
4546
|
}
|
|
3880
4547
|
}
|
|
4548
|
+
this.#afterPty(cmd);
|
|
4549
|
+
}
|
|
4550
|
+
/**
|
|
4551
|
+
* pty 窗口接管之外的两条路:窗口里有登记中的唤醒等待者 → 交给它(忙时排队);否则原来的后台路径。
|
|
4552
|
+
* 🔴 一条指令只走一条路:交给等待者之后的任何结局都由 WakeHub 负责,不再回到这里
|
|
4553
|
+
*/
|
|
4554
|
+
#afterPty(cmd) {
|
|
4555
|
+
if (cmd.tool === "claude_code" && cmd.sessionId && !isExpired(cmd, this.#deps.now())) {
|
|
4556
|
+
const sid = resolveAlias(cmd.sessionId);
|
|
4557
|
+
if (this.#wake.submit(cmd, sid)) return;
|
|
4558
|
+
}
|
|
3881
4559
|
this.#dispatch(cmd);
|
|
3882
4560
|
}
|
|
3883
4561
|
/**
|
|
@@ -3954,7 +4632,7 @@ var Daemon = class {
|
|
|
3954
4632
|
const fallback = () => {
|
|
3955
4633
|
if (fellBack) return;
|
|
3956
4634
|
fellBack = true;
|
|
3957
|
-
this.#
|
|
4635
|
+
this.#afterPty(cmd);
|
|
3958
4636
|
};
|
|
3959
4637
|
const req2 = { instructionId: cmd.instructionId, sessionId, text: cmd.text, expireAt: cmd.expireAt };
|
|
3960
4638
|
let r;
|
|
@@ -3998,7 +4676,14 @@ var Daemon = class {
|
|
|
3998
4676
|
return;
|
|
3999
4677
|
}
|
|
4000
4678
|
if (target.sessionId) this.#coord.cancelSession(target.sessionId);
|
|
4001
|
-
this.#queue.enqueue(
|
|
4679
|
+
this.#queue.enqueue(
|
|
4680
|
+
target.sessionId,
|
|
4681
|
+
() => this.#execute(cmd, target.sessionId, target.cwd, target.handleKey),
|
|
4682
|
+
(e) => {
|
|
4683
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u65F6\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
|
|
4684
|
+
this.#report(cmd, target.sessionId, "failed", "", "exec_failed");
|
|
4685
|
+
}
|
|
4686
|
+
);
|
|
4002
4687
|
}
|
|
4003
4688
|
async #execute(cmd, sessionId, cwd, handleKey = cmd.tool) {
|
|
4004
4689
|
if (sessionId) {
|
|
@@ -4050,7 +4735,13 @@ var Daemon = class {
|
|
|
4050
4735
|
return;
|
|
4051
4736
|
}
|
|
4052
4737
|
const origin = this.#runsDone.get(r.runId) ?? Promise.resolve();
|
|
4053
|
-
void origin.then(
|
|
4738
|
+
void origin.then(
|
|
4739
|
+
() => this.#queue.enqueue(
|
|
4740
|
+
r.sessionId,
|
|
4741
|
+
() => this.#resume(r, kind),
|
|
4742
|
+
(e) => log(`\u7EED\u8DD1\u4F1A\u8BDD ${r.sessionId} \u51FA\u9519\uFF1A${e?.stack ?? String(e)}`)
|
|
4743
|
+
)
|
|
4744
|
+
);
|
|
4054
4745
|
}
|
|
4055
4746
|
async #resume(r, kind) {
|
|
4056
4747
|
if (kind === "deferred" && r.consumed) {
|
|
@@ -4119,7 +4810,7 @@ var Daemon = class {
|
|
|
4119
4810
|
}
|
|
4120
4811
|
};
|
|
4121
4812
|
function newRunId() {
|
|
4122
|
-
return
|
|
4813
|
+
return randomBytes6(12).toString("hex");
|
|
4123
4814
|
}
|
|
4124
4815
|
async function runDaemon(deps = {}) {
|
|
4125
4816
|
const d = new Daemon(deps);
|
|
@@ -4137,7 +4828,7 @@ async function runDaemonCommand(opts = {}) {
|
|
|
4137
4828
|
}
|
|
4138
4829
|
if (opts.foreground) process.env.DESKBUDDY_DAEMON_FOREGROUND = "1";
|
|
4139
4830
|
try {
|
|
4140
|
-
|
|
4831
|
+
writeFileSync14(pidFilePath(), String(process.pid), "utf8");
|
|
4141
4832
|
} catch {
|
|
4142
4833
|
}
|
|
4143
4834
|
let daemon;
|
|
@@ -4153,7 +4844,7 @@ async function runDaemonCommand(opts = {}) {
|
|
|
4153
4844
|
log(`\u6536\u5230 ${sig}\uFF0C\u9000\u51FA`);
|
|
4154
4845
|
daemon.stop();
|
|
4155
4846
|
try {
|
|
4156
|
-
|
|
4847
|
+
rmSync9(pidFilePath(), { force: true });
|
|
4157
4848
|
} catch {
|
|
4158
4849
|
}
|
|
4159
4850
|
process.exit(0);
|
|
@@ -4170,8 +4861,8 @@ async function runDaemonCommand(opts = {}) {
|
|
|
4170
4861
|
}
|
|
4171
4862
|
|
|
4172
4863
|
// src/core/registry.ts
|
|
4173
|
-
import { mkdirSync as
|
|
4174
|
-
import { join as
|
|
4864
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync15 } from "fs";
|
|
4865
|
+
import { join as join18 } from "path";
|
|
4175
4866
|
var FALLBACK = {
|
|
4176
4867
|
version: "builtin",
|
|
4177
4868
|
tools: {
|
|
@@ -4249,11 +4940,11 @@ var FALLBACK = {
|
|
|
4249
4940
|
}
|
|
4250
4941
|
};
|
|
4251
4942
|
function registryCachePath() {
|
|
4252
|
-
return
|
|
4943
|
+
return join18(deskbuddyHome(), "tool-registry.json");
|
|
4253
4944
|
}
|
|
4254
4945
|
function loadRegistry() {
|
|
4255
4946
|
try {
|
|
4256
|
-
return JSON.parse(
|
|
4947
|
+
return JSON.parse(readFileSync14(registryCachePath(), "utf8"));
|
|
4257
4948
|
} catch {
|
|
4258
4949
|
return FALLBACK;
|
|
4259
4950
|
}
|
|
@@ -4261,15 +4952,15 @@ function loadRegistry() {
|
|
|
4261
4952
|
async function refreshRegistry() {
|
|
4262
4953
|
try {
|
|
4263
4954
|
const registry = await api.toolRegistry();
|
|
4264
|
-
|
|
4265
|
-
|
|
4955
|
+
mkdirSync15(deskbuddyHome(), { recursive: true });
|
|
4956
|
+
writeFileSync15(registryCachePath(), JSON.stringify(registry, null, 2), "utf8");
|
|
4266
4957
|
} catch {
|
|
4267
4958
|
}
|
|
4268
4959
|
}
|
|
4269
4960
|
|
|
4270
4961
|
// src/adapters/ccHook.ts
|
|
4271
|
-
import { chmodSync as
|
|
4272
|
-
import { dirname as dirname6, join as
|
|
4962
|
+
import { chmodSync as chmodSync5, existsSync as existsSync11, mkdirSync as mkdirSync16, readFileSync as readFileSync15, renameSync as renameSync12, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync16 } from "fs";
|
|
4963
|
+
import { dirname as dirname6, join as join19 } from "path";
|
|
4273
4964
|
var CC_EVENTS = [
|
|
4274
4965
|
"UserPromptSubmit",
|
|
4275
4966
|
"PreToolUse",
|
|
@@ -4280,7 +4971,7 @@ var CC_EVENTS = [
|
|
|
4280
4971
|
];
|
|
4281
4972
|
function readSettings(path) {
|
|
4282
4973
|
if (!existsSync11(path)) return {};
|
|
4283
|
-
const raw =
|
|
4974
|
+
const raw = readFileSync15(path, "utf8");
|
|
4284
4975
|
if (raw.trim() === "") return {};
|
|
4285
4976
|
try {
|
|
4286
4977
|
return JSON.parse(raw);
|
|
@@ -4289,17 +4980,17 @@ function readSettings(path) {
|
|
|
4289
4980
|
}
|
|
4290
4981
|
}
|
|
4291
4982
|
function writeSettings(path, settings) {
|
|
4292
|
-
|
|
4983
|
+
mkdirSync16(dirname6(path), { recursive: true });
|
|
4293
4984
|
let mode;
|
|
4294
4985
|
try {
|
|
4295
|
-
mode =
|
|
4986
|
+
mode = statSync5(path).mode & 511;
|
|
4296
4987
|
} catch {
|
|
4297
4988
|
mode = void 0;
|
|
4298
4989
|
}
|
|
4299
4990
|
const tmp = path + ".deskbuddy.tmp";
|
|
4300
|
-
|
|
4301
|
-
if (mode !== void 0)
|
|
4302
|
-
|
|
4991
|
+
writeFileSync16(tmp, JSON.stringify(settings, null, 2), "utf8");
|
|
4992
|
+
if (mode !== void 0) chmodSync5(tmp, mode);
|
|
4993
|
+
renameSync12(tmp, path);
|
|
4303
4994
|
}
|
|
4304
4995
|
function mentions(command, script) {
|
|
4305
4996
|
return !!command && (command.includes(script) || command.includes(fwdSlash(script)));
|
|
@@ -4333,25 +5024,98 @@ function installCcWindowGate(settingsPath, gatePath, tool = "claude_code", platf
|
|
|
4333
5024
|
});
|
|
4334
5025
|
writeSettings(settingsPath, settings);
|
|
4335
5026
|
}
|
|
5027
|
+
var REWAKE_TIMEOUT_S = 20 * 24 * 60 * 60;
|
|
5028
|
+
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";
|
|
5029
|
+
var REWAKE_SUMMARY = "DeskBuddy \u8BBE\u5907\u6307\u4EE4";
|
|
5030
|
+
var REWAKE_EVENTS = ["SessionStart", "Stop"];
|
|
5031
|
+
var REWAKE_CONTEXT_TIMEOUT_S = 10;
|
|
5032
|
+
function rewakeCommand(wakePath, platform = process.platform) {
|
|
5033
|
+
if (platform === "win32") return `node ${winCmdPath(wakePath)}; exit $LASTEXITCODE`;
|
|
5034
|
+
return `node ${shQuote2(wakePath)}`;
|
|
5035
|
+
}
|
|
5036
|
+
function rewakeContextCommand(wakePath, platform = process.platform) {
|
|
5037
|
+
return `node ${platform === "win32" ? winCmdPath(wakePath) : shQuote2(wakePath)} --context`;
|
|
5038
|
+
}
|
|
5039
|
+
function rewakeKind(h) {
|
|
5040
|
+
return /\s--context(\s|$)/.test(h.command) ? "context" : "wait";
|
|
5041
|
+
}
|
|
5042
|
+
function installCcRewake(settingsPath, wakePath, platform = process.platform) {
|
|
5043
|
+
const settings = readSettings(settingsPath);
|
|
5044
|
+
settings.hooks ??= {};
|
|
5045
|
+
const entries = {
|
|
5046
|
+
wait: {
|
|
5047
|
+
type: "command",
|
|
5048
|
+
command: rewakeCommand(wakePath, platform),
|
|
5049
|
+
asyncRewake: true,
|
|
5050
|
+
timeout: REWAKE_TIMEOUT_S,
|
|
5051
|
+
rewakeMessage: REWAKE_MESSAGE,
|
|
5052
|
+
rewakeSummary: REWAKE_SUMMARY
|
|
5053
|
+
},
|
|
5054
|
+
context: { type: "command", command: rewakeContextCommand(wakePath, platform), timeout: REWAKE_CONTEXT_TIMEOUT_S }
|
|
5055
|
+
};
|
|
5056
|
+
if (platform === "win32") {
|
|
5057
|
+
entries.wait.shell = "powershell";
|
|
5058
|
+
entries.context.shell = "powershell";
|
|
5059
|
+
}
|
|
5060
|
+
const wanted = { SessionStart: ["context", "wait"], Stop: ["wait"] };
|
|
5061
|
+
let changed = false;
|
|
5062
|
+
for (const event of REWAKE_EVENTS) {
|
|
5063
|
+
const groups = settings.hooks[event] ??= [];
|
|
5064
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5065
|
+
for (const g of groups) {
|
|
5066
|
+
const hooks = g.hooks ?? [];
|
|
5067
|
+
for (let i = hooks.length - 1; i >= 0; i--) {
|
|
5068
|
+
if (!mentions(hooks[i].command, wakePath)) continue;
|
|
5069
|
+
const kind = rewakeKind(hooks[i]);
|
|
5070
|
+
if (!wanted[event].includes(kind) || seen.has(kind)) {
|
|
5071
|
+
hooks.splice(i, 1);
|
|
5072
|
+
changed = true;
|
|
5073
|
+
continue;
|
|
5074
|
+
}
|
|
5075
|
+
seen.add(kind);
|
|
5076
|
+
if (JSON.stringify(hooks[i]) !== JSON.stringify(entries[kind])) {
|
|
5077
|
+
hooks[i] = { ...entries[kind] };
|
|
5078
|
+
changed = true;
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
}
|
|
5082
|
+
const before = groups.length;
|
|
5083
|
+
settings.hooks[event] = groups.filter((g) => (g.hooks ?? []).length > 0);
|
|
5084
|
+
if (settings.hooks[event].length !== before) changed = true;
|
|
5085
|
+
for (const kind of wanted[event]) {
|
|
5086
|
+
if (seen.has(kind)) continue;
|
|
5087
|
+
settings.hooks[event].push({ hooks: [{ ...entries[kind] }] });
|
|
5088
|
+
changed = true;
|
|
5089
|
+
}
|
|
5090
|
+
}
|
|
5091
|
+
if (changed) writeSettings(settingsPath, settings);
|
|
5092
|
+
}
|
|
4336
5093
|
function uninstallCcHook(settingsPath, hookPath) {
|
|
4337
5094
|
const settings = readSettings(settingsPath);
|
|
4338
5095
|
if (!settings.hooks) return;
|
|
4339
|
-
const gatePath =
|
|
5096
|
+
const gatePath = join19(dirname6(hookPath), "gate.cjs");
|
|
5097
|
+
const wakePath = join19(dirname6(hookPath), "wake.cjs");
|
|
4340
5098
|
for (const event of Object.keys(settings.hooks)) {
|
|
4341
5099
|
const groups = settings.hooks[event] ?? [];
|
|
4342
5100
|
const kept = [];
|
|
4343
5101
|
for (const g of groups) {
|
|
4344
|
-
const hooks = (g.hooks ?? []).filter(
|
|
5102
|
+
const hooks = (g.hooks ?? []).filter(
|
|
5103
|
+
(h) => !mentions(h.command, hookPath) && !mentions(h.command, gatePath) && !mentions(h.command, wakePath)
|
|
5104
|
+
);
|
|
4345
5105
|
if (hooks.length > 0) kept.push({ ...g, hooks });
|
|
4346
5106
|
}
|
|
4347
5107
|
settings.hooks[event] = kept;
|
|
4348
5108
|
}
|
|
4349
5109
|
writeSettings(settingsPath, settings);
|
|
5110
|
+
try {
|
|
5111
|
+
rmSync10(join19(dirname6(hookPath), "wake", "tokens"), { recursive: true, force: true });
|
|
5112
|
+
} catch {
|
|
5113
|
+
}
|
|
4350
5114
|
}
|
|
4351
5115
|
|
|
4352
5116
|
// src/adapters/codexHook.ts
|
|
4353
|
-
import { chmodSync as
|
|
4354
|
-
import { dirname as dirname7, join as
|
|
5117
|
+
import { chmodSync as chmodSync6, existsSync as existsSync12, mkdirSync as mkdirSync17, readFileSync as readFileSync16, renameSync as renameSync13, statSync as statSync6, writeFileSync as writeFileSync17 } from "fs";
|
|
5118
|
+
import { dirname as dirname7, join as join20 } from "path";
|
|
4355
5119
|
var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
|
|
4356
5120
|
var CODEX_MARKER_END = "# <<< deskbuddy <<<";
|
|
4357
5121
|
var CODEX_EVENTS = [
|
|
@@ -4364,7 +5128,7 @@ var CODEX_EVENTS = [
|
|
|
4364
5128
|
];
|
|
4365
5129
|
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
5130
|
function codexConfigPath() {
|
|
4367
|
-
return
|
|
5131
|
+
return join20(codexHome(), "config.toml");
|
|
4368
5132
|
}
|
|
4369
5133
|
function renderBlock(hookPath, events, tool, platform) {
|
|
4370
5134
|
const command = JSON.stringify(`node ${platform === "win32" ? winPsPath(hookPath) : hookPath} ${tool}`);
|
|
@@ -4383,17 +5147,17 @@ function renderBlock(hookPath, events, tool, platform) {
|
|
|
4383
5147
|
return lines2.join("\n");
|
|
4384
5148
|
}
|
|
4385
5149
|
function writeAtomic(path, content) {
|
|
4386
|
-
|
|
5150
|
+
mkdirSync17(dirname7(path), { recursive: true });
|
|
4387
5151
|
let mode;
|
|
4388
5152
|
try {
|
|
4389
|
-
mode =
|
|
5153
|
+
mode = statSync6(path).mode & 511;
|
|
4390
5154
|
} catch {
|
|
4391
5155
|
mode = void 0;
|
|
4392
5156
|
}
|
|
4393
5157
|
const tmp = path + ".deskbuddy.tmp";
|
|
4394
|
-
|
|
4395
|
-
if (mode !== void 0)
|
|
4396
|
-
|
|
5158
|
+
writeFileSync17(tmp, content, "utf8");
|
|
5159
|
+
if (mode !== void 0) chmodSync6(tmp, mode);
|
|
5160
|
+
renameSync13(tmp, path);
|
|
4397
5161
|
}
|
|
4398
5162
|
function assertSafeToAppend(raw, path) {
|
|
4399
5163
|
for (const quote of ['"""', "'''"]) {
|
|
@@ -4403,7 +5167,7 @@ function assertSafeToAppend(raw, path) {
|
|
|
4403
5167
|
}
|
|
4404
5168
|
}
|
|
4405
5169
|
function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "codex", platform = process.platform) {
|
|
4406
|
-
const raw = existsSync12(configPath2) ?
|
|
5170
|
+
const raw = existsSync12(configPath2) ? readFileSync16(configPath2, "utf8") : "";
|
|
4407
5171
|
if (raw.includes(CODEX_MARKER_BEGIN)) return;
|
|
4408
5172
|
assertSafeToAppend(raw, configPath2);
|
|
4409
5173
|
const block = renderBlock(hookPath, events, tool, platform);
|
|
@@ -4412,7 +5176,7 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
|
|
|
4412
5176
|
}
|
|
4413
5177
|
function uninstallCodexHook(configPath2) {
|
|
4414
5178
|
if (!existsSync12(configPath2)) return;
|
|
4415
|
-
const raw =
|
|
5179
|
+
const raw = readFileSync16(configPath2, "utf8");
|
|
4416
5180
|
const lines2 = raw.split("\n");
|
|
4417
5181
|
const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
|
|
4418
5182
|
if (begin === -1) return;
|
|
@@ -4426,11 +5190,11 @@ function uninstallCodexHook(configPath2) {
|
|
|
4426
5190
|
}
|
|
4427
5191
|
|
|
4428
5192
|
// src/commands/tools.ts
|
|
4429
|
-
import { existsSync as existsSync13, readFileSync as
|
|
5193
|
+
import { existsSync as existsSync13, readFileSync as readFileSync17 } from "fs";
|
|
4430
5194
|
function fileContains(path, needle) {
|
|
4431
5195
|
try {
|
|
4432
5196
|
if (!existsSync13(path)) return false;
|
|
4433
|
-
return
|
|
5197
|
+
return readFileSync17(path, "utf8").includes(needle);
|
|
4434
5198
|
} catch {
|
|
4435
5199
|
return false;
|
|
4436
5200
|
}
|
|
@@ -4454,6 +5218,12 @@ var ADAPTERS = {
|
|
|
4454
5218
|
if (tool !== "claude_code" && tool !== "qoder") return false;
|
|
4455
5219
|
installCcWindowGate(p, gate, tool);
|
|
4456
5220
|
return true;
|
|
5221
|
+
},
|
|
5222
|
+
// asyncRewake 只有 Claude Code 认(2.1.280 实测);别家当成同步 hook 会一直等
|
|
5223
|
+
installRewake: (p, wake, tool) => {
|
|
5224
|
+
if (tool !== "claude_code") return false;
|
|
5225
|
+
installCcRewake(p, wake);
|
|
5226
|
+
return true;
|
|
4457
5227
|
}
|
|
4458
5228
|
},
|
|
4459
5229
|
"codex-toml": {
|
|
@@ -4498,13 +5268,13 @@ function subscribedEvents(registry, tool, fallback) {
|
|
|
4498
5268
|
}
|
|
4499
5269
|
|
|
4500
5270
|
// src/commands/enable.ts
|
|
4501
|
-
import { dirname as dirname9, join as
|
|
5271
|
+
import { dirname as dirname9, join as join23 } from "path";
|
|
4502
5272
|
|
|
4503
5273
|
// src/wrap/shim.ts
|
|
4504
|
-
import { execFileSync as
|
|
4505
|
-
import { chmodSync as
|
|
5274
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
5275
|
+
import { chmodSync as chmodSync7, existsSync as existsSync14, mkdirSync as mkdirSync18, readdirSync as readdirSync4, readFileSync as readFileSync18, rmdirSync, rmSync as rmSync11, writeFileSync as writeFileSync18 } from "fs";
|
|
4506
5276
|
import { homedir as homedir5 } from "os";
|
|
4507
|
-
import { delimiter as delimiter2, join as
|
|
5277
|
+
import { delimiter as delimiter2, join as join21, resolve as resolve2 } from "path";
|
|
4508
5278
|
var SHIM_FALLBACK_EXIT = 213;
|
|
4509
5279
|
var WIN_SHIM_ENABLED = false;
|
|
4510
5280
|
var SHIM_COMMANDS = { claude_code: "claude", codex: "codex" };
|
|
@@ -4514,10 +5284,10 @@ var RC_END = "# <<< deskbuddy <<<";
|
|
|
4514
5284
|
var RC_FILES = [".zshrc", ".bashrc"];
|
|
4515
5285
|
var WIN_PENDING = "Windows \u7A97\u53E3\u63A5\u7BA1\u5F85\u9A8C\u8BC1\uFF0C\u6682\u672A\u5F00\u542F";
|
|
4516
5286
|
function shimDir() {
|
|
4517
|
-
return
|
|
5287
|
+
return join21(deskbuddyHome(), "bin");
|
|
4518
5288
|
}
|
|
4519
5289
|
function shimFile(binDir, command, platform = process.platform) {
|
|
4520
|
-
return
|
|
5290
|
+
return join21(binDir, platform === "win32" ? `${command}.cmd` : command);
|
|
4521
5291
|
}
|
|
4522
5292
|
function shQuote3(s) {
|
|
4523
5293
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
@@ -4646,10 +5416,10 @@ function removeRcBlock(content) {
|
|
|
4646
5416
|
return content.slice(0, start) + content.slice(end);
|
|
4647
5417
|
}
|
|
4648
5418
|
function readRc(p) {
|
|
4649
|
-
return
|
|
5419
|
+
return readFileSync18(p).toString("latin1");
|
|
4650
5420
|
}
|
|
4651
5421
|
function writeRc(p, latin1) {
|
|
4652
|
-
|
|
5422
|
+
writeFileSync18(p, Buffer.from(latin1, "latin1"));
|
|
4653
5423
|
}
|
|
4654
5424
|
function toLatin1(utf8) {
|
|
4655
5425
|
return Buffer.from(utf8, "utf8").toString("latin1");
|
|
@@ -4671,7 +5441,7 @@ function removePathEntry(value, dir) {
|
|
|
4671
5441
|
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
5442
|
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
5443
|
function psWinPath() {
|
|
4674
|
-
const ps = (script, env) =>
|
|
5444
|
+
const ps = (script, env) => execFileSync8("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
|
|
4675
5445
|
encoding: "utf8",
|
|
4676
5446
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4677
5447
|
windowsHide: true,
|
|
@@ -4718,12 +5488,12 @@ function installShim(tool, e) {
|
|
|
4718
5488
|
if (!node || !cli || !bin) {
|
|
4719
5489
|
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
5490
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
5491
|
+
mkdirSync18(e.binDir, { recursive: true });
|
|
5492
|
+
writeFileSync18(file, cmdScript(tool, command, node, cli, bin), "utf8");
|
|
4723
5493
|
} else {
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
5494
|
+
mkdirSync18(e.binDir, { recursive: true, mode: 448 });
|
|
5495
|
+
writeFileSync18(file, shScript(tool, command, e.nodePath, e.cliPath, e.binDir), { encoding: "utf8", mode: 493 });
|
|
5496
|
+
chmodSync7(file, 493);
|
|
4727
5497
|
}
|
|
4728
5498
|
const touched = [];
|
|
4729
5499
|
if (e.platform === "win32") {
|
|
@@ -4737,7 +5507,7 @@ function installShim(tool, e) {
|
|
|
4737
5507
|
} else if (e.rcHome) {
|
|
4738
5508
|
const block = toLatin1(rcBlock(e.binDir, e.rcHome));
|
|
4739
5509
|
for (const name of RC_FILES) {
|
|
4740
|
-
const rc =
|
|
5510
|
+
const rc = join21(e.rcHome, name);
|
|
4741
5511
|
if (!existsSync14(rc)) continue;
|
|
4742
5512
|
const next = addRcBlock(readRc(rc), block);
|
|
4743
5513
|
if (next !== null) writeRc(rc, next);
|
|
@@ -4758,13 +5528,13 @@ function installShim(tool, e) {
|
|
|
4758
5528
|
function ourShims(binDir) {
|
|
4759
5529
|
let names;
|
|
4760
5530
|
try {
|
|
4761
|
-
names =
|
|
5531
|
+
names = readdirSync4(binDir);
|
|
4762
5532
|
} catch {
|
|
4763
5533
|
return [];
|
|
4764
5534
|
}
|
|
4765
5535
|
return names.filter((n) => {
|
|
4766
5536
|
try {
|
|
4767
|
-
return
|
|
5537
|
+
return readFileSync18(join21(binDir, n), "utf8").includes(SHIM_MARKER);
|
|
4768
5538
|
} catch {
|
|
4769
5539
|
return false;
|
|
4770
5540
|
}
|
|
@@ -4776,8 +5546,8 @@ function uninstallShim(tool, e) {
|
|
|
4776
5546
|
let removed = false;
|
|
4777
5547
|
const file = shimFile(e.binDir, command, e.platform);
|
|
4778
5548
|
try {
|
|
4779
|
-
if (existsSync14(file) &&
|
|
4780
|
-
|
|
5549
|
+
if (existsSync14(file) && readFileSync18(file, "utf8").includes(SHIM_MARKER)) {
|
|
5550
|
+
rmSync11(file, { force: true });
|
|
4781
5551
|
removed = true;
|
|
4782
5552
|
}
|
|
4783
5553
|
} catch {
|
|
@@ -4795,7 +5565,7 @@ function uninstallShim(tool, e) {
|
|
|
4795
5565
|
}
|
|
4796
5566
|
} else if (e.rcHome) {
|
|
4797
5567
|
for (const name of RC_FILES) {
|
|
4798
|
-
const rc =
|
|
5568
|
+
const rc = join21(e.rcHome, name);
|
|
4799
5569
|
if (!existsSync14(rc)) continue;
|
|
4800
5570
|
const next = removeRcBlock(readRc(rc));
|
|
4801
5571
|
if (next !== null) {
|
|
@@ -4805,7 +5575,7 @@ function uninstallShim(tool, e) {
|
|
|
4805
5575
|
}
|
|
4806
5576
|
}
|
|
4807
5577
|
try {
|
|
4808
|
-
if (existsSync14(e.binDir) &&
|
|
5578
|
+
if (existsSync14(e.binDir) && readdirSync4(e.binDir).length === 0) rmdirSync(e.binDir);
|
|
4809
5579
|
} catch {
|
|
4810
5580
|
}
|
|
4811
5581
|
if (!removed && touched.length === 0) return { ok: true, message: "" };
|
|
@@ -4817,7 +5587,7 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
|
|
|
4817
5587
|
const file = shimFile(binDir, command, platform);
|
|
4818
5588
|
let installed = false;
|
|
4819
5589
|
try {
|
|
4820
|
-
installed =
|
|
5590
|
+
installed = readFileSync18(file, "utf8").includes(SHIM_MARKER);
|
|
4821
5591
|
} catch {
|
|
4822
5592
|
installed = false;
|
|
4823
5593
|
}
|
|
@@ -4828,16 +5598,16 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
|
|
|
4828
5598
|
}
|
|
4829
5599
|
|
|
4830
5600
|
// src/wrap/pty.ts
|
|
4831
|
-
import { chmodSync as
|
|
5601
|
+
import { chmodSync as chmodSync8, existsSync as existsSync15, statSync as statSync7 } from "fs";
|
|
4832
5602
|
import { createRequire as createRequire2 } from "module";
|
|
4833
|
-
import { dirname as dirname8, join as
|
|
5603
|
+
import { dirname as dirname8, join as join22 } from "path";
|
|
4834
5604
|
var req = createRequire2(import.meta.url);
|
|
4835
5605
|
function fixSpawnHelper() {
|
|
4836
5606
|
if (process.platform !== "darwin") return;
|
|
4837
5607
|
try {
|
|
4838
|
-
const dir =
|
|
4839
|
-
const helper =
|
|
4840
|
-
if (existsSync15(helper) && (
|
|
5608
|
+
const dir = join22(dirname8(req.resolve("node-pty/package.json")), "prebuilds", `darwin-${process.arch}`);
|
|
5609
|
+
const helper = join22(dir, "spawn-helper");
|
|
5610
|
+
if (existsSync15(helper) && (statSync7(helper).mode & 73) === 0) chmodSync8(helper, 493);
|
|
4841
5611
|
} catch {
|
|
4842
5612
|
}
|
|
4843
5613
|
}
|
|
@@ -4932,12 +5702,19 @@ function installTools(tools, out, shim = defaultShimEnvForInstall()) {
|
|
|
4932
5702
|
installed.push(tool);
|
|
4933
5703
|
out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
|
|
4934
5704
|
try {
|
|
4935
|
-
if (adapter.installWindowGate?.(configPath2,
|
|
5705
|
+
if (adapter.installWindowGate?.(configPath2, join23(dirname9(hookPath), "gate.cjs"), tool)) {
|
|
4936
5706
|
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
5707
|
}
|
|
4938
5708
|
} catch (e) {
|
|
4939
5709
|
out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
|
|
4940
5710
|
}
|
|
5711
|
+
try {
|
|
5712
|
+
if (adapter.installRewake?.(configPath2, join23(dirname9(hookPath), "wake.cjs"), tool)) {
|
|
5713
|
+
out(` \u7A97\u53E3\u5F00\u7740\u65F6\uFF0C\u8BBE\u5907\u6307\u4EE4\u76F4\u63A5\u5728\u8FD9\u4E2A\u7A97\u53E3\u91CC\u6267\u884C\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
|
|
5714
|
+
}
|
|
5715
|
+
} catch (e) {
|
|
5716
|
+
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`);
|
|
5717
|
+
}
|
|
4941
5718
|
if (adapter.notice) out(adapter.notice);
|
|
4942
5719
|
}
|
|
4943
5720
|
cfg.enabledTools = [...enabled];
|
|
@@ -5032,8 +5809,8 @@ async function runDisable(tools, opts = {}) {
|
|
|
5032
5809
|
}
|
|
5033
5810
|
|
|
5034
5811
|
// src/commands/install.ts
|
|
5035
|
-
import { mkdirSync as
|
|
5036
|
-
import { join as
|
|
5812
|
+
import { mkdirSync as mkdirSync19, rmSync as rmSync12, writeFileSync as writeFileSync19 } from "fs";
|
|
5813
|
+
import { join as join24 } from "path";
|
|
5037
5814
|
|
|
5038
5815
|
// src/core/detect.ts
|
|
5039
5816
|
import { existsSync as existsSync16 } from "fs";
|
|
@@ -5078,7 +5855,7 @@ function detectTools(d = defaultDetectDeps()) {
|
|
|
5078
5855
|
import qrcode from "qrcode-terminal";
|
|
5079
5856
|
|
|
5080
5857
|
// src/commands/remoteCwd.ts
|
|
5081
|
-
import { existsSync as existsSync17, statSync as
|
|
5858
|
+
import { existsSync as existsSync17, statSync as statSync8 } from "fs";
|
|
5082
5859
|
import { createInterface } from "readline";
|
|
5083
5860
|
import { homedir as homedir6 } from "os";
|
|
5084
5861
|
import { isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
|
|
@@ -5092,7 +5869,7 @@ function normalizePath(raw, home = homedir6()) {
|
|
|
5092
5869
|
function checkPath(abs) {
|
|
5093
5870
|
if (!existsSync17(abs)) return "missing";
|
|
5094
5871
|
try {
|
|
5095
|
-
return
|
|
5872
|
+
return statSync8(abs).isDirectory() ? "ok" : "not_a_dir";
|
|
5096
5873
|
} catch {
|
|
5097
5874
|
return "missing";
|
|
5098
5875
|
}
|
|
@@ -5294,11 +6071,11 @@ function inSandbox(env) {
|
|
|
5294
6071
|
}
|
|
5295
6072
|
function precheckWritable(dirs) {
|
|
5296
6073
|
for (const dir of dirs) {
|
|
5297
|
-
const probe =
|
|
6074
|
+
const probe = join24(dir, `.deskbuddy-probe-${process.pid}`);
|
|
5298
6075
|
try {
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
6076
|
+
mkdirSync19(dir, { recursive: true });
|
|
6077
|
+
writeFileSync19(probe, "");
|
|
6078
|
+
rmSync12(probe, { force: true });
|
|
5302
6079
|
} catch (e) {
|
|
5303
6080
|
return { ok: false, dir, error: e.message };
|
|
5304
6081
|
}
|
|
@@ -5427,7 +6204,7 @@ async function runInstall(opts, deps = defaultInstallDeps()) {
|
|
|
5427
6204
|
|
|
5428
6205
|
// src/commands/logout.ts
|
|
5429
6206
|
import { createInterface as createInterface2 } from "readline";
|
|
5430
|
-
import { existsSync as existsSync18, rmSync as
|
|
6207
|
+
import { existsSync as existsSync18, rmSync as rmSync13 } from "fs";
|
|
5431
6208
|
function stdout4(line) {
|
|
5432
6209
|
process.stdout.write(line + "\n");
|
|
5433
6210
|
}
|
|
@@ -5459,7 +6236,7 @@ async function runLogout(opts = {}) {
|
|
|
5459
6236
|
}
|
|
5460
6237
|
await runDisable([], { out, brief: true, keepAutostart: opts.keepAutostart });
|
|
5461
6238
|
if (existsSync18(home)) {
|
|
5462
|
-
|
|
6239
|
+
rmSync13(home, { recursive: true, force: true });
|
|
5463
6240
|
}
|
|
5464
6241
|
out("");
|
|
5465
6242
|
out("\u2705 \u5DF2\u6E05\u9664\u672C\u673A\u7684 DeskBuddy \u6570\u636E\u3002");
|
|
@@ -5619,9 +6396,9 @@ async function runUpdate(opts = {}) {
|
|
|
5619
6396
|
}
|
|
5620
6397
|
|
|
5621
6398
|
// src/wrap/run.ts
|
|
5622
|
-
import { randomBytes as
|
|
5623
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
5624
|
-
import { join as
|
|
6399
|
+
import { randomBytes as randomBytes7 } from "crypto";
|
|
6400
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync21, statSync as statSync10, truncateSync as truncateSync3 } from "fs";
|
|
6401
|
+
import { join as join26 } from "path";
|
|
5625
6402
|
import { StringDecoder } from "string_decoder";
|
|
5626
6403
|
|
|
5627
6404
|
// src/wrap/screen.ts
|
|
@@ -5976,20 +6753,20 @@ function runDirect(real2, args, env = process.env) {
|
|
|
5976
6753
|
|
|
5977
6754
|
// src/wrap/codexLaunch.ts
|
|
5978
6755
|
import { spawn as spawn3 } from "child_process";
|
|
5979
|
-
import { closeSync, mkdirSync as
|
|
6756
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync20, openSync as openSync2, readdirSync as readdirSync5, readFileSync as readFileSync19, rmSync as rmSync14, statSync as statSync9, truncateSync as truncateSync2, writeFileSync as writeFileSync20, renameSync as renameSync14 } from "fs";
|
|
5980
6757
|
import { request } from "http";
|
|
5981
|
-
import { createServer as
|
|
5982
|
-
import { join as
|
|
6758
|
+
import { createServer as createServer4 } from "net";
|
|
6759
|
+
import { join as join25 } from "path";
|
|
5983
6760
|
|
|
5984
6761
|
// src/core/codexServer.ts
|
|
5985
|
-
import { execFileSync as
|
|
6762
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
5986
6763
|
function isAppServerArgs(args, url) {
|
|
5987
6764
|
return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
|
|
5988
6765
|
}
|
|
5989
6766
|
function processArgs(pid) {
|
|
5990
6767
|
if (process.platform === "win32") return null;
|
|
5991
6768
|
try {
|
|
5992
|
-
const out =
|
|
6769
|
+
const out = execFileSync9("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
|
|
5993
6770
|
encoding: "utf8",
|
|
5994
6771
|
stdio: ["ignore", "pipe", "ignore"]
|
|
5995
6772
|
});
|
|
@@ -6147,10 +6924,10 @@ async function runCodexWindow(real2, args, d) {
|
|
|
6147
6924
|
}
|
|
6148
6925
|
var MAX_LOG_BYTES2 = 1024 * 1024;
|
|
6149
6926
|
function appServerLogPath() {
|
|
6150
|
-
return
|
|
6927
|
+
return join25(deskbuddyHome(), "codex-app-server.log");
|
|
6151
6928
|
}
|
|
6152
6929
|
function windowsDir2() {
|
|
6153
|
-
return
|
|
6930
|
+
return join25(deskbuddyHome(), "codex-windows");
|
|
6154
6931
|
}
|
|
6155
6932
|
function pidAlive(pid) {
|
|
6156
6933
|
try {
|
|
@@ -6163,7 +6940,7 @@ function pidAlive(pid) {
|
|
|
6163
6940
|
function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
|
|
6164
6941
|
let pid = 0;
|
|
6165
6942
|
try {
|
|
6166
|
-
pid = Number(
|
|
6943
|
+
pid = Number(readFileSync19(pidFile, "utf8").trim());
|
|
6167
6944
|
} catch {
|
|
6168
6945
|
return false;
|
|
6169
6946
|
}
|
|
@@ -6185,7 +6962,7 @@ function probeReady(url) {
|
|
|
6185
6962
|
}
|
|
6186
6963
|
function freePort() {
|
|
6187
6964
|
return new Promise((resolve5, reject) => {
|
|
6188
|
-
const srv =
|
|
6965
|
+
const srv = createServer4();
|
|
6189
6966
|
srv.unref();
|
|
6190
6967
|
srv.on("error", reject);
|
|
6191
6968
|
srv.listen(0, "127.0.0.1", () => {
|
|
@@ -6197,12 +6974,12 @@ function freePort() {
|
|
|
6197
6974
|
}
|
|
6198
6975
|
function startServer(real2, url, env, cwd) {
|
|
6199
6976
|
const logPath2 = appServerLogPath();
|
|
6200
|
-
|
|
6977
|
+
mkdirSync20(deskbuddyHome(), { recursive: true });
|
|
6201
6978
|
try {
|
|
6202
|
-
if (
|
|
6979
|
+
if (statSync9(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
|
|
6203
6980
|
} catch {
|
|
6204
6981
|
}
|
|
6205
|
-
const fd =
|
|
6982
|
+
const fd = openSync2(logPath2, "a");
|
|
6206
6983
|
try {
|
|
6207
6984
|
const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
|
|
6208
6985
|
delete childEnv2.DESKBUDDY_SHIM;
|
|
@@ -6217,7 +6994,7 @@ function startServer(real2, url, env, cwd) {
|
|
|
6217
6994
|
child.unref();
|
|
6218
6995
|
return child.pid ?? null;
|
|
6219
6996
|
} finally {
|
|
6220
|
-
|
|
6997
|
+
closeSync2(fd);
|
|
6221
6998
|
}
|
|
6222
6999
|
}
|
|
6223
7000
|
function killServer(pid) {
|
|
@@ -6231,41 +7008,41 @@ function killOwnServer(pid, url) {
|
|
|
6231
7008
|
if (a && isAppServerArgs(a, url)) killServer(pid);
|
|
6232
7009
|
}
|
|
6233
7010
|
function recordPath(rec) {
|
|
6234
|
-
return
|
|
7011
|
+
return join25(windowsDir2(), `${rec.server}.json`);
|
|
6235
7012
|
}
|
|
6236
7013
|
function writeRecord(rec) {
|
|
6237
7014
|
try {
|
|
6238
|
-
|
|
7015
|
+
mkdirSync20(windowsDir2(), { recursive: true, mode: 448 });
|
|
6239
7016
|
const p = recordPath(rec);
|
|
6240
|
-
|
|
6241
|
-
|
|
7017
|
+
writeFileSync20(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
|
|
7018
|
+
renameSync14(p + ".tmp", p);
|
|
6242
7019
|
} catch {
|
|
6243
7020
|
}
|
|
6244
7021
|
}
|
|
6245
7022
|
function removeRecord(rec) {
|
|
6246
7023
|
try {
|
|
6247
|
-
|
|
7024
|
+
rmSync14(recordPath(rec), { force: true });
|
|
6248
7025
|
} catch {
|
|
6249
7026
|
}
|
|
6250
7027
|
}
|
|
6251
7028
|
function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs, kill = killServer) {
|
|
6252
7029
|
let names = [];
|
|
6253
7030
|
try {
|
|
6254
|
-
names =
|
|
7031
|
+
names = readdirSync5(dir).filter((n) => n.endsWith(".json"));
|
|
6255
7032
|
} catch {
|
|
6256
7033
|
return;
|
|
6257
7034
|
}
|
|
6258
7035
|
for (const n of names) {
|
|
6259
|
-
const p =
|
|
7036
|
+
const p = join25(dir, n);
|
|
6260
7037
|
let rec;
|
|
6261
7038
|
try {
|
|
6262
|
-
rec = JSON.parse(
|
|
7039
|
+
rec = JSON.parse(readFileSync19(p, "utf8"));
|
|
6263
7040
|
} catch {
|
|
6264
|
-
|
|
7041
|
+
rmSync14(p, { force: true });
|
|
6265
7042
|
continue;
|
|
6266
7043
|
}
|
|
6267
7044
|
if (typeof rec.supervisor !== "number" || typeof rec.server !== "number" || typeof rec.url !== "string") {
|
|
6268
|
-
|
|
7045
|
+
rmSync14(p, { force: true });
|
|
6269
7046
|
continue;
|
|
6270
7047
|
}
|
|
6271
7048
|
if (alive(rec.supervisor)) continue;
|
|
@@ -6273,13 +7050,13 @@ function reapWindows(dir = windowsDir2(), alive = pidAlive, args = processArgs,
|
|
|
6273
7050
|
const a = args(rec.server);
|
|
6274
7051
|
if (a && isAppServerArgs(a, rec.url)) kill(rec.server);
|
|
6275
7052
|
}
|
|
6276
|
-
|
|
7053
|
+
rmSync14(p, { force: true });
|
|
6277
7054
|
}
|
|
6278
7055
|
}
|
|
6279
7056
|
function launchLog(line) {
|
|
6280
7057
|
try {
|
|
6281
|
-
|
|
6282
|
-
|
|
7058
|
+
mkdirSync20(deskbuddyHome(), { recursive: true });
|
|
7059
|
+
writeFileSync20(join25(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
|
|
6283
7060
|
`, { flag: "a" });
|
|
6284
7061
|
} catch {
|
|
6285
7062
|
}
|
|
@@ -6319,10 +7096,10 @@ function childEnv(src, windowId) {
|
|
|
6319
7096
|
}
|
|
6320
7097
|
function wrapLog(line) {
|
|
6321
7098
|
try {
|
|
6322
|
-
const p =
|
|
6323
|
-
|
|
7099
|
+
const p = join26(deskbuddyHome(), "wrap.log");
|
|
7100
|
+
mkdirSync21(deskbuddyHome(), { recursive: true });
|
|
6324
7101
|
try {
|
|
6325
|
-
if (
|
|
7102
|
+
if (statSync10(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
|
|
6326
7103
|
} catch {
|
|
6327
7104
|
}
|
|
6328
7105
|
appendFileSync2(p, `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${line}
|
|
@@ -6409,7 +7186,7 @@ async function wrapInPty(tool, real2, args, deps) {
|
|
|
6409
7186
|
return null;
|
|
6410
7187
|
}
|
|
6411
7188
|
const { stdout: stdout7 } = deps;
|
|
6412
|
-
const windowId =
|
|
7189
|
+
const windowId = randomBytes7(16).toString("hex");
|
|
6413
7190
|
const cols = stdout7.columns || 80;
|
|
6414
7191
|
const rows = stdout7.rows || 24;
|
|
6415
7192
|
const term = new Headless({ cols, rows, allowProposedApi: true });
|