deskbuddy 0.2.0-beta.13 → 0.2.0-beta.14
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 +2241 -359
- package/dist/gate.cjs +21 -13
- package/dist/hook.cjs +287 -9
- package/dist/ocTranslate.mjs +106 -0
- package/dist/opencode.mjs +100 -69
- package/hook/gate.cjs +21 -13
- package/hook/hook-debug.log +5 -0
- package/hook/hook.cjs +287 -9
- package/hook/ocTranslate.d.mts +7 -0
- package/hook/ocTranslate.mjs +106 -0
- package/hook/opencode.d.mts +4 -1
- package/hook/opencode.mjs +100 -69
- package/package.json +3 -2
- package/vendor/node-pty/LICENSE +69 -0
- package/vendor/node-pty/lib/conpty_console_list_agent.js +16 -0
- package/vendor/node-pty/lib/eventEmitter2.js +47 -0
- package/vendor/node-pty/lib/index.js +52 -0
- package/vendor/node-pty/lib/interfaces.js +7 -0
- package/vendor/node-pty/lib/shared/conout.js +11 -0
- package/vendor/node-pty/lib/terminal.js +190 -0
- package/vendor/node-pty/lib/types.js +7 -0
- package/vendor/node-pty/lib/unixTerminal.js +346 -0
- package/vendor/node-pty/lib/utils.js +39 -0
- package/vendor/node-pty/lib/windowsConoutConnection.js +125 -0
- package/vendor/node-pty/lib/windowsPtyAgent.js +320 -0
- package/vendor/node-pty/lib/windowsTerminal.js +199 -0
- package/vendor/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/vendor/node-pty/package.json +7 -0
- package/vendor/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/vendor/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/vendor/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/vendor/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/vendor/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
- package/vendor/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
- package/vendor/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/vendor/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/vendor/node-pty/prebuilds/win32-arm64/pty.node +0 -0
- package/vendor/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
- package/vendor/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
- package/vendor/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
- package/vendor/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
- package/vendor/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/vendor/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/vendor/node-pty/prebuilds/win32-x64/pty.node +0 -0
- package/vendor/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
- package/vendor/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
package/dist/cli.js
CHANGED
|
@@ -61,6 +61,15 @@ function workbuddySettingsPath() {
|
|
|
61
61
|
function qoderCnSettingsPath() {
|
|
62
62
|
return join(process.env.DESKBUDDY_QODER_CN_DIR || join(homedir(), ".qoder-cn"), "settings.json");
|
|
63
63
|
}
|
|
64
|
+
function qwenworkHome() {
|
|
65
|
+
return process.env.DESKBUDDY_QWENWORK_DIR || join(homedir(), ".QwenWorkCN");
|
|
66
|
+
}
|
|
67
|
+
function qwenworkSettingsPath() {
|
|
68
|
+
return join(qwenworkHome(), "settings.json");
|
|
69
|
+
}
|
|
70
|
+
function qianfanHome() {
|
|
71
|
+
return process.env.DESKBUDDY_QIANFAN_DIR || join(homedir(), ".qianfan");
|
|
72
|
+
}
|
|
64
73
|
function qoderSettingsPath() {
|
|
65
74
|
return join(process.env.DESKBUDDY_QODER_DIR || join(homedir(), ".qoder"), "settings.json");
|
|
66
75
|
}
|
|
@@ -169,7 +178,7 @@ function atomicCopy(from, to) {
|
|
|
169
178
|
function syncHookFile(opts = {}) {
|
|
170
179
|
const target = opts.target ?? installedHookPath();
|
|
171
180
|
const version = opts.version ?? packageVersion();
|
|
172
|
-
const SIBLINGS = ["gate.cjs", "opencode.mjs", "wake.cjs", "launcher.cjs"];
|
|
181
|
+
const SIBLINGS = ["gate.cjs", "ocTranslate.mjs", "opencode.mjs", "wake.cjs", "launcher.cjs"];
|
|
173
182
|
const siblingMissing = () => SIBLINGS.some((name) => {
|
|
174
183
|
if (existsSync2(join2(dirname(target), name))) return false;
|
|
175
184
|
try {
|
|
@@ -1007,25 +1016,57 @@ import { dirname as dirname5, join as join6, posix, win32 } from "path";
|
|
|
1007
1016
|
import { chmodSync, existsSync as existsSync5, statSync } from "fs";
|
|
1008
1017
|
import { createRequire as createRequire2 } from "module";
|
|
1009
1018
|
import { dirname as dirname4, join as join5 } from "path";
|
|
1019
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1010
1020
|
var req = createRequire2(import.meta.url);
|
|
1011
|
-
|
|
1021
|
+
var VENDORED_PTY_PLATFORMS = ["darwin-arm64", "darwin-x64", "win32-arm64", "win32-x64"];
|
|
1022
|
+
function hasVendoredPty(platform = process.platform, arch = process.arch) {
|
|
1023
|
+
return VENDORED_PTY_PLATFORMS.includes(`${platform}-${arch}`);
|
|
1024
|
+
}
|
|
1025
|
+
function vendoredPtyDir(from = dirname4(fileURLToPath3(import.meta.url))) {
|
|
1026
|
+
let dir = from;
|
|
1027
|
+
for (let i = 0; i < 4; i++) {
|
|
1028
|
+
const cand = join5(dir, "vendor", "node-pty");
|
|
1029
|
+
if (existsSync5(join5(cand, "package.json"))) return cand;
|
|
1030
|
+
const up = dirname4(dir);
|
|
1031
|
+
if (up === dir) break;
|
|
1032
|
+
dir = up;
|
|
1033
|
+
}
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
function fixSpawnHelper(ptyDir) {
|
|
1012
1037
|
if (process.platform !== "darwin") return;
|
|
1013
1038
|
try {
|
|
1014
|
-
const
|
|
1015
|
-
const helper = join5(dir, "spawn-helper");
|
|
1039
|
+
const helper = join5(ptyDir, "prebuilds", `darwin-${process.arch}`, "spawn-helper");
|
|
1016
1040
|
if (existsSync5(helper) && (statSync(helper).mode & 73) === 0) chmodSync(helper, 493);
|
|
1017
1041
|
} catch {
|
|
1018
1042
|
}
|
|
1019
1043
|
}
|
|
1044
|
+
function asPty(m) {
|
|
1045
|
+
return typeof m?.spawn === "function" ? m : null;
|
|
1046
|
+
}
|
|
1020
1047
|
function loadPty() {
|
|
1048
|
+
if (hasVendoredPty()) {
|
|
1049
|
+
const dir = vendoredPtyDir();
|
|
1050
|
+
if (dir) {
|
|
1051
|
+
try {
|
|
1052
|
+
fixSpawnHelper(dir);
|
|
1053
|
+
const m = asPty(req(dir));
|
|
1054
|
+
if (m) return m;
|
|
1055
|
+
} catch {
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1021
1059
|
try {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
return
|
|
1060
|
+
const dir = dirname4(req.resolve("node-pty/package.json"));
|
|
1061
|
+
fixSpawnHelper(dir);
|
|
1062
|
+
return asPty(req(dir));
|
|
1025
1063
|
} catch {
|
|
1026
1064
|
return null;
|
|
1027
1065
|
}
|
|
1028
1066
|
}
|
|
1067
|
+
function ptyMissingReason(platform = process.platform, arch = process.arch) {
|
|
1068
|
+
return hasVendoredPty(platform, arch) ? "\u5185\u7F6E\u7684 node-pty \u52A0\u8F7D\u5931\u8D25" : `${platform === "linux" ? "Linux" : `${platform}-${arch}`} \u4E0B\u6CA1\u6709\u5185\u7F6E node-pty \u9884\u7F16\u8BD1\uFF0C\u7A97\u53E3\u63A5\u7BA1\u4E0D\u53EF\u7528`;
|
|
1069
|
+
}
|
|
1029
1070
|
function loadHeadless() {
|
|
1030
1071
|
try {
|
|
1031
1072
|
const m = req("@xterm/headless");
|
|
@@ -1123,17 +1164,32 @@ function npmInstallArgs(npmCli, dir, target, registry) {
|
|
|
1123
1164
|
if (!REGISTRIES.includes(registry)) throw new Error(`\u4E0D\u5141\u8BB8\u7684\u6E90\uFF1A${registry}`);
|
|
1124
1165
|
return [npmCli, "install", "--prefix", dir, "--no-audit", "--no-fund", "--omit=dev", `${PACKAGE_NAME}@${target}`, "--registry", registry];
|
|
1125
1166
|
}
|
|
1126
|
-
|
|
1167
|
+
function installEnv(execPath, base = process.env, platform = process.platform) {
|
|
1168
|
+
const env = { ...base };
|
|
1169
|
+
const win = platform === "win32";
|
|
1170
|
+
const key = (win ? Object.keys(env).find((k) => k.toUpperCase() === "PATH") : void 0) ?? "PATH";
|
|
1171
|
+
const nodeDir = (win ? win32 : posix).dirname(execPath);
|
|
1172
|
+
const sep3 = win ? ";" : ":";
|
|
1173
|
+
const rest = (env[key] ?? "").split(sep3).filter((p) => p && p !== nodeDir);
|
|
1174
|
+
env[key] = [nodeDir, ...rest].join(sep3);
|
|
1175
|
+
return { ...env, npm_config_update_notifier: "false", npm_config_fund: "false" };
|
|
1176
|
+
}
|
|
1177
|
+
var defaultExec = (file, args, opts) => new Promise((resolve7, reject) => {
|
|
1127
1178
|
execFile2(file, args, { ...opts, maxBuffer: 16 * 1024 * 1024 }, (err, _stdout, stderr) => {
|
|
1128
1179
|
if (err) {
|
|
1129
1180
|
const tail = String(stderr || "").trim().split("\n").slice(-3).join(" | ");
|
|
1130
1181
|
reject(new Error(`${err.message.split("\n")[0]}${tail ? ` \u2014 ${tail.slice(0, 300)}` : ""}`));
|
|
1131
|
-
} else
|
|
1182
|
+
} else resolve7();
|
|
1132
1183
|
});
|
|
1133
1184
|
});
|
|
1185
|
+
function ptyCandidatesIn(dir, platform = process.platform, arch = process.arch) {
|
|
1186
|
+
const pkg = join6(dir, "node_modules", PACKAGE_NAME);
|
|
1187
|
+
return [...hasVendoredPty(platform, arch) ? [join6(pkg, "vendor", "node-pty")] : [], join6(dir, "node_modules", "node-pty")];
|
|
1188
|
+
}
|
|
1134
1189
|
function ptyLoadsIn(dir, execPath = process.execPath) {
|
|
1190
|
+
const script = "for(const p of process.argv.slice(1)){try{const m=require(p);if(typeof m.spawn==='function')process.exit(0)}catch{}}process.exit(3)";
|
|
1135
1191
|
try {
|
|
1136
|
-
execFileSync4(execPath, ["-e",
|
|
1192
|
+
execFileSync4(execPath, ["-e", script, ...ptyCandidatesIn(dir)], {
|
|
1137
1193
|
timeout: 3e4,
|
|
1138
1194
|
windowsHide: true,
|
|
1139
1195
|
shell: false,
|
|
@@ -1170,7 +1226,7 @@ async function installVersion(target, deps) {
|
|
|
1170
1226
|
timeout: INSTALL_TIMEOUT_MS,
|
|
1171
1227
|
shell: false,
|
|
1172
1228
|
windowsHide: true,
|
|
1173
|
-
env:
|
|
1229
|
+
env: installEnv(execPath, process.env, platform)
|
|
1174
1230
|
});
|
|
1175
1231
|
} catch (e) {
|
|
1176
1232
|
errors.push(`${registry} ${e.message}`);
|
|
@@ -1187,7 +1243,8 @@ async function installVersion(target, deps) {
|
|
|
1187
1243
|
continue;
|
|
1188
1244
|
}
|
|
1189
1245
|
if (oldPtyOk() && !newPtyOk(dir)) {
|
|
1190
|
-
|
|
1246
|
+
const installed = ptyCandidatesIn(dir).some((p) => existsSync6(join6(p, "package.json")));
|
|
1247
|
+
errors.push(`${registry} \u88C5\u597D\u7684\u7248\u672C\u52A0\u8F7D\u4E0D\u4E86 node-pty\uFF08${installed ? "\u5305\u91CC\u6709\u4F46\u52A0\u8F7D\u5931\u8D25" : "\u5305\u91CC\u6CA1\u6709\uFF0Cnpm \u4E5F\u8DF3\u8FC7\u4E86\u53EF\u9009\u4F9D\u8D56"}\uFF09`);
|
|
1191
1248
|
continue;
|
|
1192
1249
|
}
|
|
1193
1250
|
return cliPath;
|
|
@@ -1294,7 +1351,7 @@ var AutoUpdater = class {
|
|
|
1294
1351
|
return "failed";
|
|
1295
1352
|
}
|
|
1296
1353
|
log2(`${target} \u5DF2\u88C5\u597D\u5E76\u6821\u9A8C\u901A\u8FC7\uFF0C\u7B49\u5B88\u62A4\u8FDB\u7A0B\u7A7A\u95F2\u540E\u5207\u6362`);
|
|
1297
|
-
const
|
|
1354
|
+
const sleep3 = this.#d.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms).unref?.()));
|
|
1298
1355
|
const deadline = this.#now() + (this.#d.idleMaxWaitMs ?? IDLE_MAX_WAIT_MS);
|
|
1299
1356
|
let lastWhy = "";
|
|
1300
1357
|
for (; ; ) {
|
|
@@ -1309,7 +1366,7 @@ var AutoUpdater = class {
|
|
|
1309
1366
|
log2(`\u7B49\u4E86 24 \u5C0F\u65F6\u4ECD\u4E0D\u7A7A\u95F2\uFF0C\u653E\u5F03\u8FD9\u4E00\u8F6E\uFF0C\u4E0B\u6B21\u68C0\u67E5\u518D\u5224\u5B9A`);
|
|
1310
1367
|
return "timeout";
|
|
1311
1368
|
}
|
|
1312
|
-
await
|
|
1369
|
+
await sleep3(this.#d.idlePollMs ?? IDLE_POLL_MS);
|
|
1313
1370
|
}
|
|
1314
1371
|
if (this.#stopped) return "stopped";
|
|
1315
1372
|
this.#d.beginDrain?.();
|
|
@@ -1342,7 +1399,7 @@ var AutoUpdater = class {
|
|
|
1342
1399
|
// src/daemon/index.ts
|
|
1343
1400
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
1344
1401
|
import { existsSync as existsSync13 } from "fs";
|
|
1345
|
-
import { basename as
|
|
1402
|
+
import { basename as basename8, dirname as dirname10, join as join26 } from "path";
|
|
1346
1403
|
import WebSocket2 from "ws";
|
|
1347
1404
|
|
|
1348
1405
|
// src/adapters/ocPlugin.ts
|
|
@@ -1407,6 +1464,31 @@ function processName(pid) {
|
|
|
1407
1464
|
return null;
|
|
1408
1465
|
}
|
|
1409
1466
|
}
|
|
1467
|
+
function parentPid(pid) {
|
|
1468
|
+
if (WIN) {
|
|
1469
|
+
const pp = winProcs()?.find((p) => p.pid === pid)?.ppid ?? 0;
|
|
1470
|
+
return pp > 4 && pp !== pid ? pp : null;
|
|
1471
|
+
}
|
|
1472
|
+
try {
|
|
1473
|
+
const out = execFileSync5("ps", ["-p", String(pid), "-o", "ppid="], {
|
|
1474
|
+
encoding: "utf8",
|
|
1475
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1476
|
+
});
|
|
1477
|
+
const ppid = Number(out.trim());
|
|
1478
|
+
return Number.isFinite(ppid) && ppid > 1 ? ppid : null;
|
|
1479
|
+
} catch {
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
function selfAndAncestors(pid, maxDepth = 8) {
|
|
1484
|
+
const out = [];
|
|
1485
|
+
let cur = pid;
|
|
1486
|
+
for (let i = 0; i <= maxDepth && cur != null && !out.includes(cur); i++) {
|
|
1487
|
+
out.push(cur);
|
|
1488
|
+
cur = parentPid(cur);
|
|
1489
|
+
}
|
|
1490
|
+
return out;
|
|
1491
|
+
}
|
|
1410
1492
|
function isAlive(pid, expectName) {
|
|
1411
1493
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
1412
1494
|
try {
|
|
@@ -1440,6 +1522,7 @@ function writeEnded(s, now = Date.now()) {
|
|
|
1440
1522
|
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
1441
1523
|
const rec = { sessionId: s.sessionId, tool: s.tool, cwd: s.cwd ?? "", interactive: s.interactive === true, endedAt: now };
|
|
1442
1524
|
if (Number.isInteger(s.turns) && s.turns >= 0) rec.turns = s.turns;
|
|
1525
|
+
if (typeof s.firstPrompt === "string" && s.firstPrompt) rec.firstPrompt = s.firstPrompt;
|
|
1443
1526
|
const file = join8(dir, `${s.sessionId}.json`);
|
|
1444
1527
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
1445
1528
|
writeFileSync6(tmp, JSON.stringify(rec), { encoding: "utf8", mode: 384 });
|
|
@@ -1562,6 +1645,12 @@ function readState(sessionId) {
|
|
|
1562
1645
|
return null;
|
|
1563
1646
|
}
|
|
1564
1647
|
}
|
|
1648
|
+
function writeState(s) {
|
|
1649
|
+
mkdirSync8(stateDir(), { recursive: true });
|
|
1650
|
+
const tmp = statePath(s.sessionId) + ".tmp";
|
|
1651
|
+
writeFileSync7(tmp, JSON.stringify(s), "utf8");
|
|
1652
|
+
renameSync6(tmp, statePath(s.sessionId));
|
|
1653
|
+
}
|
|
1565
1654
|
function retireState(s, now = Date.now()) {
|
|
1566
1655
|
writeEnded(s, now);
|
|
1567
1656
|
removeState(s.sessionId);
|
|
@@ -1572,8 +1661,26 @@ function removeState(sessionId) {
|
|
|
1572
1661
|
} catch {
|
|
1573
1662
|
}
|
|
1574
1663
|
}
|
|
1575
|
-
|
|
1664
|
+
var STALE_NO_PID_MS = 2 * 60 * 60 * 1e3;
|
|
1665
|
+
var STALE_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
|
|
1666
|
+
var STALE_NO_PID_LOG = "\u65E0\u8FDB\u7A0B\u53F7\u4E14 2 \u5C0F\u65F6\u65E0\u4E8B\u4EF6\uFF0C\u6309\u5DF2\u7ED3\u675F\u5904\u7406";
|
|
1667
|
+
function pendingApprovalSessions() {
|
|
1668
|
+
try {
|
|
1669
|
+
const recs = JSON.parse(readFileSync8(join9(deskbuddyHome(), "approvals.json"), "utf8"));
|
|
1670
|
+
return new Set(Array.isArray(recs) ? recs.filter((r) => r && r.status === "pending" && typeof r.sessionId === "string").map((r) => r.sessionId) : []);
|
|
1671
|
+
} catch {
|
|
1672
|
+
return /* @__PURE__ */ new Set();
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
function isStaleNoPid(s, now, pending) {
|
|
1676
|
+
if (s.pid || !STALE_STATES.has(s.state) || !(now - s.updatedAt > STALE_NO_PID_MS)) return false;
|
|
1677
|
+
return !pending().has(s.sessionId);
|
|
1678
|
+
}
|
|
1679
|
+
function readAliveStates(tool, opts = {}) {
|
|
1576
1680
|
if (!existsSync8(stateDir())) return [];
|
|
1681
|
+
const now = opts.now ?? Date.now();
|
|
1682
|
+
let pend = null;
|
|
1683
|
+
const pending = () => pend ??= pendingApprovalSessions();
|
|
1577
1684
|
const alive = [];
|
|
1578
1685
|
for (const file of readdirSync3(stateDir())) {
|
|
1579
1686
|
if (!file.endsWith(".json")) continue;
|
|
@@ -1589,6 +1696,14 @@ function readAliveStates(tool) {
|
|
|
1589
1696
|
retireState(s);
|
|
1590
1697
|
continue;
|
|
1591
1698
|
}
|
|
1699
|
+
if (isStaleNoPid(s, now, pending)) {
|
|
1700
|
+
try {
|
|
1701
|
+
opts.onStale?.(s);
|
|
1702
|
+
} catch {
|
|
1703
|
+
}
|
|
1704
|
+
retireState(s);
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1592
1707
|
alive.push(s);
|
|
1593
1708
|
}
|
|
1594
1709
|
return alive;
|
|
@@ -1812,7 +1927,13 @@ function parseApprovalFrame(frame) {
|
|
|
1812
1927
|
if (!isValidId(approvalId)) return null;
|
|
1813
1928
|
const decision = str(d.decision);
|
|
1814
1929
|
if (decision !== "allow" && decision !== "deny" && decision !== "allow_always") return null;
|
|
1815
|
-
return {
|
|
1930
|
+
return {
|
|
1931
|
+
instructionId,
|
|
1932
|
+
approvalId,
|
|
1933
|
+
decision,
|
|
1934
|
+
deviceName: cleanDeviceName(d.deviceName),
|
|
1935
|
+
...Array.isArray(d.answers) && d.answers.length <= 8 ? { answers: d.answers } : {}
|
|
1936
|
+
};
|
|
1816
1937
|
}
|
|
1817
1938
|
function cleanDeviceName(v) {
|
|
1818
1939
|
return typeof v === "string" ? v.replace(/[\u0000-\u001f\u007f]/g, "").trim().slice(0, 32) : "";
|
|
@@ -1828,7 +1949,7 @@ import { join as join12 } from "path";
|
|
|
1828
1949
|
|
|
1829
1950
|
// src/daemon/gate/settings.ts
|
|
1830
1951
|
import { existsSync as existsSync10 } from "fs";
|
|
1831
|
-
import { fileURLToPath as
|
|
1952
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1832
1953
|
var GATE_HOOK_TIMEOUT_S = 7 * 24 * 60 * 60;
|
|
1833
1954
|
function waitModeFor(tool) {
|
|
1834
1955
|
return tool === "claude_code" ? "defer" : "hold";
|
|
@@ -1851,8 +1972,8 @@ function winPsPath(p) {
|
|
|
1851
1972
|
}
|
|
1852
1973
|
function defaultGateScript() {
|
|
1853
1974
|
const candidates = [
|
|
1854
|
-
|
|
1855
|
-
|
|
1975
|
+
fileURLToPath4(new URL("./gate.cjs", import.meta.url)),
|
|
1976
|
+
fileURLToPath4(new URL("../../../hook/gate.cjs", import.meta.url))
|
|
1856
1977
|
];
|
|
1857
1978
|
return candidates.find((p) => existsSync10(p)) ?? candidates[0];
|
|
1858
1979
|
}
|
|
@@ -1886,6 +2007,10 @@ import { execFileSync as execFileSync6 } from "child_process";
|
|
|
1886
2007
|
import { accessSync, constants, existsSync as existsSync11, lstatSync, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
|
|
1887
2008
|
import { homedir as homedir4 } from "os";
|
|
1888
2009
|
import { delimiter, dirname as dirname7, join as join11, win32 as win323 } from "path";
|
|
2010
|
+
function qoderWorker(d, worker, appBin) {
|
|
2011
|
+
if (d.isExecutable(appBin)) return { file: appBin, prefixArgs: [worker], env: { ELECTRON_RUN_AS_NODE: "1" } };
|
|
2012
|
+
return { file: d.nodePath, prefixArgs: [worker] };
|
|
2013
|
+
}
|
|
1889
2014
|
var defaultLocateDeps = () => ({
|
|
1890
2015
|
home: homedir4(),
|
|
1891
2016
|
path: process.env.PATH ?? "",
|
|
@@ -2084,14 +2209,14 @@ function locateWin(tool, d) {
|
|
|
2084
2209
|
case "qoder_cn": {
|
|
2085
2210
|
for (const dir of winAppDirs(d, "Qoder CN")) {
|
|
2086
2211
|
const p = join11(dir, QODER_CN_WORKER_IN_WIN_APP);
|
|
2087
|
-
if (d.exists(p)) return
|
|
2212
|
+
if (d.exists(p)) return qoderWorker(d, p, join11(dir, "Qoder CN.exe"));
|
|
2088
2213
|
}
|
|
2089
2214
|
return null;
|
|
2090
2215
|
}
|
|
2091
2216
|
case "qoder": {
|
|
2092
2217
|
for (const dir of winAppDirs(d, "Qoder")) {
|
|
2093
2218
|
const p = join11(dir, QODER_WORKER_IN_WIN_APP);
|
|
2094
|
-
if (d.exists(p)) return
|
|
2219
|
+
if (d.exists(p)) return qoderWorker(d, p, join11(dir, "Qoder.exe"));
|
|
2095
2220
|
}
|
|
2096
2221
|
return plain("qodercli");
|
|
2097
2222
|
}
|
|
@@ -2128,14 +2253,14 @@ function locate(tool, d = defaultLocateDeps()) {
|
|
|
2128
2253
|
case "qoder_cn": {
|
|
2129
2254
|
for (const dir of appDirs(d.home)) {
|
|
2130
2255
|
const p = join11(dir, QODER_CN_WORKER_IN_APP);
|
|
2131
|
-
if (d.exists(p)) return
|
|
2256
|
+
if (d.exists(p)) return qoderWorker(d, p, join11(dir, "Qoder CN.app", "Contents", "MacOS", "Qoder CN"));
|
|
2132
2257
|
}
|
|
2133
2258
|
return null;
|
|
2134
2259
|
}
|
|
2135
2260
|
case "qoder": {
|
|
2136
2261
|
for (const dir of appDirs(d.home)) {
|
|
2137
2262
|
const p = join11(dir, QODER_WORKER_IN_APP);
|
|
2138
|
-
if (d.exists(p)) return
|
|
2263
|
+
if (d.exists(p)) return qoderWorker(d, p, join11(dir, "Qoder.app", "Contents", "MacOS", "Qoder"));
|
|
2139
2264
|
}
|
|
2140
2265
|
return plain("qodercli");
|
|
2141
2266
|
}
|
|
@@ -2262,7 +2387,7 @@ function materialize(plan, tool, d = defaultLocateDeps()) {
|
|
|
2262
2387
|
file: loc.file,
|
|
2263
2388
|
args: [...loc.prefixArgs, ...plan.args],
|
|
2264
2389
|
cwd: plan.cwd,
|
|
2265
|
-
env: { ...plan.env, PATH: childPath(d) }
|
|
2390
|
+
env: { ...plan.env, ...loc.env, PATH: childPath(d) }
|
|
2266
2391
|
};
|
|
2267
2392
|
}
|
|
2268
2393
|
function isDeferred(stdout7) {
|
|
@@ -2326,6 +2451,17 @@ function parseSessionId(tool, stdout7) {
|
|
|
2326
2451
|
if (tool === "opencode") return parseOpencodeSessionId(stdout7);
|
|
2327
2452
|
return parseClaudeSessionId(stdout7);
|
|
2328
2453
|
}
|
|
2454
|
+
var FAILURE_TAIL_CHARS = 500;
|
|
2455
|
+
function failureTail(r, text) {
|
|
2456
|
+
let s = (r.stderr ?? "").trim() || (r.stdout ?? "").trim();
|
|
2457
|
+
if (!s) return "";
|
|
2458
|
+
const plain = [text, text.replace(/\s+/g, " ").trim()];
|
|
2459
|
+
const forms = [...plain, ...plain.map((t) => JSON.stringify(t).slice(1, -1))].filter((t, i, a) => t.length >= 2 && a.indexOf(t) === i);
|
|
2460
|
+
forms.sort((a, b) => b.length - a.length);
|
|
2461
|
+
for (const f of forms) s = s.split(f).join("<\u6307\u4EE4>");
|
|
2462
|
+
s = s.replace(/\s+/g, " ").trim();
|
|
2463
|
+
return s.length > FAILURE_TAIL_CHARS ? "\u2026" + s.slice(-FAILURE_TAIL_CHARS) : s;
|
|
2464
|
+
}
|
|
2329
2465
|
function startCommand(plan, tool, limitMs = EXEC_TIMEOUT_MS) {
|
|
2330
2466
|
let remaining = limitMs;
|
|
2331
2467
|
let startedAt = Date.now();
|
|
@@ -2341,7 +2477,7 @@ function startCommand(plan, tool, limitMs = EXEC_TIMEOUT_MS) {
|
|
|
2341
2477
|
}, Math.max(0, remaining));
|
|
2342
2478
|
};
|
|
2343
2479
|
let timedOut = false;
|
|
2344
|
-
const done = new Promise((
|
|
2480
|
+
const done = new Promise((resolve7) => {
|
|
2345
2481
|
child = execFile3(
|
|
2346
2482
|
plan.file,
|
|
2347
2483
|
plan.args,
|
|
@@ -2353,16 +2489,18 @@ function startCommand(plan, tool, limitMs = EXEC_TIMEOUT_MS) {
|
|
|
2353
2489
|
// Windows:别给子进程弹黑框
|
|
2354
2490
|
windowsHide: true
|
|
2355
2491
|
},
|
|
2356
|
-
(err, stdout7) => {
|
|
2492
|
+
(err, stdout7, stderr) => {
|
|
2357
2493
|
finished = true;
|
|
2358
2494
|
if (timer) clearTimeout(timer);
|
|
2359
2495
|
const out = String(stdout7 ?? "");
|
|
2496
|
+
const errOut = String(stderr ?? "");
|
|
2360
2497
|
if (err || timedOut) {
|
|
2361
|
-
const
|
|
2362
|
-
|
|
2498
|
+
const e = err;
|
|
2499
|
+
const msg = timedOut ? `\u6267\u884C\u8D85\u8FC7 ${Math.round(limitMs / 1e3)} \u79D2\u6D3B\u52A8\u65F6\u95F4\uFF0C\u5DF2\u7EC8\u6B62` : e && typeof e.code === "number" ? `\u9000\u51FA\u7801 ${e.code}` : e && e.signal ? `\u88AB\u4FE1\u53F7 ${String(e.signal)} \u7ED3\u675F` : `\u8D77\u4E0D\u6765\uFF08${String(e?.code ?? "unknown")}\uFF09`;
|
|
2500
|
+
resolve7({ ok: false, sessionId: parseSessionId(tool, out), stdout: out, stderr: errOut, error: msg });
|
|
2363
2501
|
return;
|
|
2364
2502
|
}
|
|
2365
|
-
|
|
2503
|
+
resolve7({ ok: true, sessionId: parseSessionId(tool, out), stdout: out, stderr: errOut });
|
|
2366
2504
|
}
|
|
2367
2505
|
);
|
|
2368
2506
|
child?.stdin?.end();
|
|
@@ -2370,6 +2508,9 @@ function startCommand(plan, tool, limitMs = EXEC_TIMEOUT_MS) {
|
|
|
2370
2508
|
});
|
|
2371
2509
|
return {
|
|
2372
2510
|
done,
|
|
2511
|
+
get pid() {
|
|
2512
|
+
return child?.pid;
|
|
2513
|
+
},
|
|
2373
2514
|
pauseTimeout() {
|
|
2374
2515
|
if (finished) return;
|
|
2375
2516
|
if (paused++ === 0 && timer) {
|
|
@@ -2437,6 +2578,7 @@ var ApprovalStore = class _ApprovalStore {
|
|
|
2437
2578
|
}
|
|
2438
2579
|
}
|
|
2439
2580
|
save() {
|
|
2581
|
+
for (const r of this.#records) if (r.status !== "pending" && r.toolInput !== void 0) delete r.toolInput;
|
|
2440
2582
|
try {
|
|
2441
2583
|
mkdirSync10(deskbuddyHome(), { recursive: true });
|
|
2442
2584
|
const tmp = storePath() + ".tmp";
|
|
@@ -2492,7 +2634,9 @@ function serverBackend(client = api) {
|
|
|
2492
2634
|
summary: r.summary,
|
|
2493
2635
|
cwdName: basename5(r.cwd),
|
|
2494
2636
|
source: r.mode === "window" ? "window" : "remote",
|
|
2495
|
-
...r.remember ? { remember: r.remember } : {}
|
|
2637
|
+
...r.remember ? { remember: r.remember } : {},
|
|
2638
|
+
// 🔴 选择题只发展示用的题目和背景;原始 tool_input(r.toolInput)只留本机
|
|
2639
|
+
...r.kind === "question" && r.questions ? { questions: r.questions, ...r.context ? { context: r.context } : {} } : {}
|
|
2496
2640
|
}),
|
|
2497
2641
|
cancel: (approvalId, reason) => client.approvalCancel(approvalId, reason),
|
|
2498
2642
|
sync: async (ids) => {
|
|
@@ -2501,7 +2645,7 @@ function serverBackend(client = api) {
|
|
|
2501
2645
|
for (const a of res?.approvals ?? []) {
|
|
2502
2646
|
if (typeof a?.approvalId !== "string") continue;
|
|
2503
2647
|
const d = mapStatus(a.status, a.decision);
|
|
2504
|
-
if (d) out.push({ approvalId: a.approvalId, decision: d, deviceName: cleanDeviceName(a.deviceName) });
|
|
2648
|
+
if (d) out.push({ approvalId: a.approvalId, decision: d, deviceName: cleanDeviceName(a.deviceName), ...Array.isArray(a.answers) ? { answers: a.answers } : {} });
|
|
2505
2649
|
}
|
|
2506
2650
|
return out;
|
|
2507
2651
|
}
|
|
@@ -2671,6 +2815,152 @@ function describe(toolName, toolInput, cwd) {
|
|
|
2671
2815
|
return { kind: SHELL_TOOLS.has(toolName) ? "command" : "other", summary: truncate(detail ? `${toolName} ${detail}` : toolName) };
|
|
2672
2816
|
}
|
|
2673
2817
|
|
|
2818
|
+
// src/daemon/gate/question.ts
|
|
2819
|
+
import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync4 } from "fs";
|
|
2820
|
+
var MAX_QUESTIONS = 4;
|
|
2821
|
+
var MAX_OPTIONS = 4;
|
|
2822
|
+
var HEADER_MAX = 12;
|
|
2823
|
+
var QUESTION_MAX = 150;
|
|
2824
|
+
var LABEL_MAX = 40;
|
|
2825
|
+
var DESC_MAX = 80;
|
|
2826
|
+
var SUMMARY_MAX2 = 200;
|
|
2827
|
+
var CONTEXT_MAX = 300;
|
|
2828
|
+
var OTHER_MAX = 200;
|
|
2829
|
+
function clean(v) {
|
|
2830
|
+
return typeof v === "string" ? v.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, " ").replace(/\s+/g, " ").trim() : "";
|
|
2831
|
+
}
|
|
2832
|
+
function cut(s, max) {
|
|
2833
|
+
const a = Array.from(s);
|
|
2834
|
+
return a.length > max ? a.slice(0, max - 1).join("") + "\u2026" : s;
|
|
2835
|
+
}
|
|
2836
|
+
var RECOMMENDED_RE = /\s*\(Recommended\)\s*$/i;
|
|
2837
|
+
function rawQuestions(toolInput) {
|
|
2838
|
+
const ans = toolInput?.answers;
|
|
2839
|
+
if (ans !== void 0 && ans !== null) return null;
|
|
2840
|
+
const qs = toolInput?.questions;
|
|
2841
|
+
if (!Array.isArray(qs) || qs.length === 0 || qs.length > MAX_QUESTIONS) return null;
|
|
2842
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2843
|
+
for (const q of qs) {
|
|
2844
|
+
if (!q || typeof q !== "object" || typeof q.question !== "string" || !q.question.trim()) return null;
|
|
2845
|
+
const kind = q.kind;
|
|
2846
|
+
if (kind !== void 0 && kind !== "choice") return null;
|
|
2847
|
+
if (seen.has(q.question)) return null;
|
|
2848
|
+
seen.add(q.question);
|
|
2849
|
+
if (!Array.isArray(q.options) || q.options.length < 2) return null;
|
|
2850
|
+
for (const o of q.options) if (!o || typeof o !== "object" || typeof o.label !== "string" || !o.label.trim()) return null;
|
|
2851
|
+
}
|
|
2852
|
+
return qs;
|
|
2853
|
+
}
|
|
2854
|
+
function questionView(toolInput) {
|
|
2855
|
+
const raw = rawQuestions(toolInput);
|
|
2856
|
+
if (!raw) return null;
|
|
2857
|
+
const questions = raw.map((q) => {
|
|
2858
|
+
const opts = q.options.slice(0, MAX_OPTIONS).map((o) => {
|
|
2859
|
+
const recommended = RECOMMENDED_RE.test(o.label);
|
|
2860
|
+
return {
|
|
2861
|
+
label: cut(clean(o.label.replace(RECOMMENDED_RE, "")), LABEL_MAX),
|
|
2862
|
+
description: cut(clean(o.description), DESC_MAX),
|
|
2863
|
+
recommended
|
|
2864
|
+
};
|
|
2865
|
+
});
|
|
2866
|
+
return {
|
|
2867
|
+
header: cut(clean(q.header), HEADER_MAX),
|
|
2868
|
+
question: cut(clean(q.question), QUESTION_MAX),
|
|
2869
|
+
multiSelect: q.multiSelect === true,
|
|
2870
|
+
options: opts,
|
|
2871
|
+
...q.options.length > MAX_OPTIONS ? { truncatedOptions: true } : {}
|
|
2872
|
+
};
|
|
2873
|
+
});
|
|
2874
|
+
const first = questions[0];
|
|
2875
|
+
return { questions, summary: cut(first.question || first.header, SUMMARY_MAX2) };
|
|
2876
|
+
}
|
|
2877
|
+
function joinAnswerItems(items) {
|
|
2878
|
+
return items.map((s) => s.includes(", ") || s.includes('"') ? JSON.stringify(s) : s).join(", ");
|
|
2879
|
+
}
|
|
2880
|
+
function resolveAnswers(toolInput, answers) {
|
|
2881
|
+
const raw = rawQuestions(toolInput);
|
|
2882
|
+
if (!raw || !Array.isArray(answers) || answers.length !== raw.length) return null;
|
|
2883
|
+
const res = [];
|
|
2884
|
+
const done = /* @__PURE__ */ new Set();
|
|
2885
|
+
const ordered = [...answers].sort((x, y) => (x?.index ?? 0) - (y?.index ?? 0));
|
|
2886
|
+
for (const a of ordered) {
|
|
2887
|
+
if (!a || typeof a !== "object" || !Number.isInteger(a.index) || a.index < 0 || a.index >= raw.length || done.has(a.index)) return null;
|
|
2888
|
+
done.add(a.index);
|
|
2889
|
+
const q = raw[a.index];
|
|
2890
|
+
const shown = Math.min(q.options.length, MAX_OPTIONS);
|
|
2891
|
+
const sel = Array.isArray(a.selected) ? a.selected : [];
|
|
2892
|
+
if (!sel.every((i) => Number.isInteger(i) && i >= 0 && i < shown) || new Set(sel).size !== sel.length) return null;
|
|
2893
|
+
const other = clean(a.other);
|
|
2894
|
+
if (Array.from(other).length > OTHER_MAX) return null;
|
|
2895
|
+
if (other && q.custom === false) return null;
|
|
2896
|
+
const labels = [...sel].sort((x, y) => x - y).map((i) => q.options[i].label);
|
|
2897
|
+
if (q.multiSelect === true) {
|
|
2898
|
+
if (labels.length === 0 && !other) return null;
|
|
2899
|
+
const items = other ? [...labels, other] : labels;
|
|
2900
|
+
res.push({ question: q.question, items, joined: joinAnswerItems(items) });
|
|
2901
|
+
} else {
|
|
2902
|
+
if (labels.length === 1 && !other) res.push({ question: q.question, items: labels, joined: labels[0] });
|
|
2903
|
+
else if (labels.length === 0 && other) res.push({ question: q.question, items: [other], joined: other });
|
|
2904
|
+
else return null;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
return res;
|
|
2908
|
+
}
|
|
2909
|
+
function answersToUpdatedInput(toolInput, answers) {
|
|
2910
|
+
const r = resolveAnswers(toolInput, answers);
|
|
2911
|
+
if (!r) return null;
|
|
2912
|
+
const out = {};
|
|
2913
|
+
for (const x of r) out[x.question] = x.joined;
|
|
2914
|
+
return { ...toolInput, answers: out };
|
|
2915
|
+
}
|
|
2916
|
+
function answersSummary(updatedInput) {
|
|
2917
|
+
const a = updatedInput.answers;
|
|
2918
|
+
const s = Object.values(a ?? {}).join("\uFF1B");
|
|
2919
|
+
return cut(clean(s), 60);
|
|
2920
|
+
}
|
|
2921
|
+
var TAIL_BYTES = 256 * 1024;
|
|
2922
|
+
function lastAssistantText(transcriptPath) {
|
|
2923
|
+
if (typeof transcriptPath !== "string" || !transcriptPath) return "";
|
|
2924
|
+
let text = "";
|
|
2925
|
+
try {
|
|
2926
|
+
const size = statSync4(transcriptPath).size;
|
|
2927
|
+
const len = Math.min(size, TAIL_BYTES);
|
|
2928
|
+
const buf = Buffer.alloc(len);
|
|
2929
|
+
const fd = openSync3(transcriptPath, "r");
|
|
2930
|
+
try {
|
|
2931
|
+
readSync2(fd, buf, 0, len, size - len);
|
|
2932
|
+
} finally {
|
|
2933
|
+
closeSync3(fd);
|
|
2934
|
+
}
|
|
2935
|
+
text = buf.toString("utf8");
|
|
2936
|
+
} catch {
|
|
2937
|
+
return "";
|
|
2938
|
+
}
|
|
2939
|
+
const lines2 = text.split("\n");
|
|
2940
|
+
for (let i = lines2.length - 1; i >= 0; i--) {
|
|
2941
|
+
const line = lines2[i].trim();
|
|
2942
|
+
if (!line.startsWith("{")) continue;
|
|
2943
|
+
try {
|
|
2944
|
+
const m = JSON.parse(line);
|
|
2945
|
+
if (m.type === "user") {
|
|
2946
|
+
const c = m.message?.content;
|
|
2947
|
+
const toolResultOnly = Array.isArray(c) && c.length > 0 && c.every((x) => x?.type === "tool_result");
|
|
2948
|
+
if (!toolResultOnly) return "";
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
if (m.type !== "assistant" || !Array.isArray(m.message?.content)) continue;
|
|
2952
|
+
const parts = m.message.content.filter((c) => c && c.type === "text" && typeof c.text === "string").map((c) => c.text);
|
|
2953
|
+
const body = parts.join("\n").trim();
|
|
2954
|
+
if (!body) continue;
|
|
2955
|
+
const paras = body.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean);
|
|
2956
|
+
return cut(clean(paras[paras.length - 1] ?? body), CONTEXT_MAX);
|
|
2957
|
+
} catch {
|
|
2958
|
+
continue;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
return "";
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2674
2964
|
// src/daemon/gate/procs.ts
|
|
2675
2965
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
2676
2966
|
function listDescendants(rootPid) {
|
|
@@ -2785,7 +3075,17 @@ function windowNotice(decision, summary2, deviceName = "") {
|
|
|
2785
3075
|
const what = summary2.length > 60 ? summary2.slice(0, 59) + "\u2026" : summary2;
|
|
2786
3076
|
return decision === "allow" ? `\u2705 \u5DF2\u5728${who}\u4E0A\u5141\u8BB8\uFF1A${what}` : `\u274C \u5DF2\u5728${who}\u4E0A\u62D2\u7EDD\uFF1A${what}`;
|
|
2787
3077
|
}
|
|
2788
|
-
var WINDOW_SKIP_TOOLS = /* @__PURE__ */ new Set(["
|
|
3078
|
+
var WINDOW_SKIP_TOOLS = /* @__PURE__ */ new Set(["ExitPlanMode"]);
|
|
3079
|
+
var QUESTION_TOOL = "AskUserQuestion";
|
|
3080
|
+
var QUESTION_TOOLS_HOSTS = /* @__PURE__ */ new Set(["claude_code", "qoder_cn", "qoder", "codex", "opencode", "qwenwork", "dumate"]);
|
|
3081
|
+
var QUESTION_SKIPPED = "\u7528\u6237\u5728\u8BBE\u5907\u4E0A\u9009\u62E9\u4E0D\u56DE\u7B54\u8FD9\u4E2A\u95EE\u9898\uFF0C\u8BF7\u5728\u7535\u8111\u4E0A\u8BE2\u95EE\u6216\u6309\u4F60\u7684\u5224\u65AD\u7EE7\u7EED";
|
|
3082
|
+
var QUESTION_UNANSWERABLE = "\u8BBE\u5907\u672A\u7ED9\u51FA\u6709\u6548\u7B54\u6848\uFF08\u8FD9\u4E2A\u95EE\u9898\u6CA1\u6CD5\u5728\u8BBE\u5907\u4E0A\u56DE\u7B54\uFF09\uFF0C\u8BF7\u6309\u4F60\u7684\u5224\u65AD\u7EE7\u7EED\uFF0C\u6216\u5728\u7ED3\u679C\u91CC\u8BF4\u660E\u9700\u8981\u7528\u6237\u51B3\u5B9A\u7684\u4E8B";
|
|
3083
|
+
var REMOTE_QUESTION_TOOLS = /* @__PURE__ */ new Set(["workbuddy"]);
|
|
3084
|
+
var NO_NOTICE_TOOLS = /* @__PURE__ */ new Set(["qwenwork", "dumate"]);
|
|
3085
|
+
function questionNotice(summary2, deviceName = "") {
|
|
3086
|
+
const who = deviceName ? `\u300C${deviceName}\u300D` : DEVICE_FALLBACK;
|
|
3087
|
+
return `\u2705 \u5DF2\u5728${who}\u4E0A\u56DE\u7B54\uFF1A${summary2}`;
|
|
3088
|
+
}
|
|
2789
3089
|
var WINDOW_SETTLED_EVENTS = /* @__PURE__ */ new Set(["UserPromptSubmit", "Stop"]);
|
|
2790
3090
|
var WINDOW_TOOL_DONE_EVENTS = /* @__PURE__ */ new Set(["PostToolUse", "PostToolUseFailure"]);
|
|
2791
3091
|
var WINDOW_PROC_WATCH_MS = 10 * 60 * 1e3;
|
|
@@ -2823,7 +3123,10 @@ var ApprovalCoordinator = class {
|
|
|
2823
3123
|
const toolName = typeof req2.hook.tool_name === "string" ? req2.hook.tool_name : "";
|
|
2824
3124
|
const sessionId = typeof req2.hook.session_id === "string" && req2.hook.session_id || run2.sessionId || "";
|
|
2825
3125
|
if (sessionId && !run2.sessionId) run2.sessionId = sessionId;
|
|
2826
|
-
const
|
|
3126
|
+
const remoteQuestion = toolName === QUESTION_TOOL && REMOTE_QUESTION_TOOLS.has(run2.tool) && run2.mode === "hold";
|
|
3127
|
+
const qv = remoteQuestion ? questionView(req2.hook.tool_input) : null;
|
|
3128
|
+
if (remoteQuestion && !qv) return { decision: "deny", reason: QUESTION_UNANSWERABLE };
|
|
3129
|
+
const v = qv ? { action: "ask", kind: "question", summary: qv.summary } : classify(toolName, req2.hook.tool_input, cwdForms(run2.cwd), this.#d.policy(), { real: realpathNearest });
|
|
2827
3130
|
if (v.action === "auto") return { decision: "allow" };
|
|
2828
3131
|
const fp = fingerprint(toolName, req2.hook.tool_input);
|
|
2829
3132
|
const store = this.#d.store;
|
|
@@ -2848,13 +3151,17 @@ var ApprovalCoordinator = class {
|
|
|
2848
3151
|
summary: v.summary,
|
|
2849
3152
|
mode: run2.mode,
|
|
2850
3153
|
status: "pending",
|
|
2851
|
-
createdAt: this.#now()
|
|
3154
|
+
createdAt: this.#now(),
|
|
3155
|
+
...qv ? (() => {
|
|
3156
|
+
const context = lastAssistantText(req2.hook.transcript_path);
|
|
3157
|
+
return { questions: qv.questions, toolInput: req2.hook.tool_input, ...context ? { context } : {} };
|
|
3158
|
+
})() : {}
|
|
2852
3159
|
};
|
|
2853
3160
|
store.add(rec);
|
|
2854
3161
|
store.save();
|
|
2855
3162
|
let early = null;
|
|
2856
3163
|
let settle;
|
|
2857
|
-
const decided = new Promise((
|
|
3164
|
+
const decided = new Promise((resolve7) => settle = resolve7);
|
|
2858
3165
|
this.#waiters.set(rec.approvalId, (r) => {
|
|
2859
3166
|
early = r;
|
|
2860
3167
|
settle(r);
|
|
@@ -2878,8 +3185,8 @@ var ApprovalCoordinator = class {
|
|
|
2878
3185
|
}
|
|
2879
3186
|
run2.onWaitStart?.();
|
|
2880
3187
|
try {
|
|
2881
|
-
return await new Promise((
|
|
2882
|
-
void decided.then(
|
|
3188
|
+
return await new Promise((resolve7) => {
|
|
3189
|
+
void decided.then(resolve7);
|
|
2883
3190
|
const onAbort = () => {
|
|
2884
3191
|
if (!this.#waiters.delete(rec.approvalId)) return;
|
|
2885
3192
|
if (rec.status === "pending") {
|
|
@@ -2888,7 +3195,7 @@ var ApprovalCoordinator = class {
|
|
|
2888
3195
|
store.save();
|
|
2889
3196
|
void this.#cancelRemote(rec.approvalId, "\u667A\u80FD\u4F53\u8FDB\u7A0B\u5DF2\u7ED3\u675F");
|
|
2890
3197
|
}
|
|
2891
|
-
|
|
3198
|
+
resolve7({ decision: "deny", reason: "\u5DF2\u53D6\u6D88" });
|
|
2892
3199
|
};
|
|
2893
3200
|
if (signal.aborted) onAbort();
|
|
2894
3201
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -2909,10 +3216,14 @@ var ApprovalCoordinator = class {
|
|
|
2909
3216
|
const sessionId = typeof hook.session_id === "string" ? hook.session_id : "";
|
|
2910
3217
|
const cwd = typeof hook.cwd === "string" ? hook.cwd : "";
|
|
2911
3218
|
if (!toolName || !sessionId || WINDOW_SKIP_TOOLS.has(toolName)) return none;
|
|
3219
|
+
const isQuestion = toolName === QUESTION_TOOL;
|
|
3220
|
+
if (isQuestion && !QUESTION_TOOLS_HOSTS.has(tool)) return none;
|
|
3221
|
+
const qv = isQuestion ? questionView(hook.tool_input) : null;
|
|
3222
|
+
if (isQuestion && !qv) return none;
|
|
2912
3223
|
for (const run2 of this.#runs.values()) if (run2.sessionId === sessionId) return none;
|
|
2913
3224
|
const store = this.#d.store;
|
|
2914
|
-
const { kind, summary: summary2 } = describe(toolName, hook.tool_input, cwd ? cwdForms(cwd) : []);
|
|
2915
|
-
const remember = REMEMBER_TOOLS.has(tool) ? pickRememberSuggestion(hook.permission_suggestions, { edit: EDIT_TOOLS2.has(toolName) || kind === "edit" }) : null;
|
|
3225
|
+
const { kind, summary: summary2 } = qv ? { kind: "question", summary: qv.summary } : describe(toolName, hook.tool_input, cwd ? cwdForms(cwd) : []);
|
|
3226
|
+
const remember = !qv && REMEMBER_TOOLS.has(tool) ? pickRememberSuggestion(hook.permission_suggestions, { edit: EDIT_TOOLS2.has(toolName) || kind === "edit" }) : null;
|
|
2916
3227
|
const rec = {
|
|
2917
3228
|
approvalId: randomUUID3(),
|
|
2918
3229
|
runId: "",
|
|
@@ -2925,7 +3236,11 @@ var ApprovalCoordinator = class {
|
|
|
2925
3236
|
mode: "window",
|
|
2926
3237
|
status: "pending",
|
|
2927
3238
|
createdAt: this.#now(),
|
|
2928
|
-
...remember ? { remember: remember.text, rememberUpdates: remember.updates } : {}
|
|
3239
|
+
...remember ? { remember: remember.text, rememberUpdates: remember.updates } : {},
|
|
3240
|
+
...qv ? (() => {
|
|
3241
|
+
const context = lastAssistantText(hook.transcript_path);
|
|
3242
|
+
return { questions: qv.questions, toolInput: hook.tool_input, ...context ? { context } : {} };
|
|
3243
|
+
})() : {}
|
|
2929
3244
|
};
|
|
2930
3245
|
store.add(rec);
|
|
2931
3246
|
store.save();
|
|
@@ -2940,7 +3255,7 @@ var ApprovalCoordinator = class {
|
|
|
2940
3255
|
...watchProcs ? { pid, cmd, baseline: new Set(this.#descendants(pid).map((p) => p.pid)) } : {}
|
|
2941
3256
|
});
|
|
2942
3257
|
let settle;
|
|
2943
|
-
const decided = new Promise((
|
|
3258
|
+
const decided = new Promise((resolve7) => settle = resolve7);
|
|
2944
3259
|
this.#waiters.set(rec.approvalId, settle);
|
|
2945
3260
|
try {
|
|
2946
3261
|
await this.#d.backend.create(rec);
|
|
@@ -2956,11 +3271,11 @@ var ApprovalCoordinator = class {
|
|
|
2956
3271
|
}
|
|
2957
3272
|
this.#d.log(`\u7A97\u53E3\u6388\u6743 ${rec.approvalId} \u5DF2\u63A8\u5230\u8BBE\u5907 kind=${rec.kind}`);
|
|
2958
3273
|
if (rec.status !== "pending") void this.#cancelRemote(rec.approvalId, "\u7535\u8111\u4E0A\u5DF2\u5904\u7406");
|
|
2959
|
-
return new Promise((
|
|
2960
|
-
void decided.then(
|
|
3274
|
+
return new Promise((resolve7) => {
|
|
3275
|
+
void decided.then(resolve7);
|
|
2961
3276
|
const onAbort = () => {
|
|
2962
3277
|
if (this.#waiters.has(rec.approvalId)) this.#settleWindow(rec, "hook \u5DF2\u7ED3\u675F");
|
|
2963
|
-
|
|
3278
|
+
resolve7(none);
|
|
2964
3279
|
};
|
|
2965
3280
|
if (signal.aborted) onAbort();
|
|
2966
3281
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
@@ -2989,8 +3304,10 @@ var ApprovalCoordinator = class {
|
|
|
2989
3304
|
}
|
|
2990
3305
|
const fp = rec.fingerprint.slice(0, 16);
|
|
2991
3306
|
const settled = (st.recent ?? []).some(
|
|
2992
|
-
(e) => e.at > rec.createdAt && (WINDOW_SETTLED_EVENTS.has(e.e) || // 跑完的必须正是这一步:并行的同名工具不算(评审 #2)。老版 hook 没有 fp
|
|
2993
|
-
|
|
3307
|
+
(e) => e.at > rec.createdAt && (WINDOW_SETTLED_EVENTS.has(e.e) || // 跑完的必须正是这一步:并行的同名工具不算(评审 #2)。老版 hook 没有 fp 时退回只比工具名。
|
|
3308
|
+
// 选择题只比工具名:答完后 PostToolUse 的 tool_input 带上了 answers,指纹变了(spec §3.5 / R2);
|
|
3309
|
+
// 同一会话同时只会有一道选择题(子代理用不了 AskUserQuestion,窗口里一次也只弹一个选择框)
|
|
3310
|
+
WINDOW_TOOL_DONE_EVENTS.has(e.e) && e.tool === w.toolName && (e.fp === void 0 || e.fp === fp || rec.kind === "question"))
|
|
2994
3311
|
);
|
|
2995
3312
|
if (settled) {
|
|
2996
3313
|
this.#settleWindow(rec, "\u7535\u8111\u4E0A\u5DF2\u5904\u7406");
|
|
@@ -3026,6 +3343,56 @@ var ApprovalCoordinator = class {
|
|
|
3026
3343
|
}
|
|
3027
3344
|
void this.#cancelRemote(rec.approvalId, reason);
|
|
3028
3345
|
}
|
|
3346
|
+
/**
|
|
3347
|
+
* 选择题的答复:作答 → 用本地原始 tool_input 按下标拼 updatedInput 交回 hook;跳过 → deny(不带设备名)。
|
|
3348
|
+
* 🔴 答案拼不出来(下标越界、单选给了多个……)一律不表态,窗口里的选择框照常由人答(红线 R7)
|
|
3349
|
+
*/
|
|
3350
|
+
#onQuestionDecision(rec, decision, deviceName, answers) {
|
|
3351
|
+
rec.decidedAt = this.#now();
|
|
3352
|
+
rec.consumed = true;
|
|
3353
|
+
this.#windows.delete(rec.approvalId);
|
|
3354
|
+
const waiter = this.#waiters.get(rec.approvalId);
|
|
3355
|
+
this.#waiters.delete(rec.approvalId);
|
|
3356
|
+
const remote = rec.mode !== "window";
|
|
3357
|
+
if (decision === "deny") {
|
|
3358
|
+
rec.status = "denied";
|
|
3359
|
+
this.#d.store.save();
|
|
3360
|
+
if (!waiter) return;
|
|
3361
|
+
if (remote) return waiter({ decision: "deny", reason: QUESTION_SKIPPED });
|
|
3362
|
+
const notice2 = `\u23ED \u5DF2\u5728${deviceName ? `\u300C${deviceName}\u300D` : DEVICE_FALLBACK}\u4E0A\u8DF3\u8FC7\uFF1A${rec.summary.slice(0, 60)}`;
|
|
3363
|
+
if (!NO_NOTICE_TOOLS.has(rec.tool)) {
|
|
3364
|
+
try {
|
|
3365
|
+
;
|
|
3366
|
+
(this.#d.writeNotice ?? writeNotice)(rec.sessionId, notice2);
|
|
3367
|
+
} catch {
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
waiter({ decision: "deny", reason: QUESTION_SKIPPED, notice: notice2 });
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
const resolved = resolveAnswers(rec.toolInput, answers);
|
|
3374
|
+
const updatedInput = resolved ? answersToUpdatedInput(rec.toolInput, answers) : null;
|
|
3375
|
+
if (!resolved || !updatedInput) {
|
|
3376
|
+
rec.status = "cancelled";
|
|
3377
|
+
this.#d.store.save();
|
|
3378
|
+
this.#d.log(`\u9009\u62E9\u9898 ${rec.approvalId} \u7684\u7B54\u6848\u5BF9\u4E0D\u4E0A\u9898\u76EE\uFF0C${remote ? "\u62D2\u7EDD" : "\u4E0D\u8868\u6001"}`);
|
|
3379
|
+
waiter?.(remote ? { decision: "deny", reason: QUESTION_UNANSWERABLE } : { decision: "none" });
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
rec.status = "approved";
|
|
3383
|
+
this.#d.store.save();
|
|
3384
|
+
if (!waiter) return;
|
|
3385
|
+
if (remote) return waiter({ decision: "allow", updatedInput });
|
|
3386
|
+
const notice = questionNotice(answersSummary(updatedInput), deviceName);
|
|
3387
|
+
if (!NO_NOTICE_TOOLS.has(rec.tool)) {
|
|
3388
|
+
try {
|
|
3389
|
+
;
|
|
3390
|
+
(this.#d.writeNotice ?? writeNotice)(rec.sessionId, notice);
|
|
3391
|
+
} catch {
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
waiter({ decision: "allow", notice, updatedInput, answerLists: resolved.map((r) => r.items) });
|
|
3395
|
+
}
|
|
3029
3396
|
#readState(sessionId) {
|
|
3030
3397
|
try {
|
|
3031
3398
|
return (this.#d.readState ?? readState)(sessionId);
|
|
@@ -3033,14 +3400,18 @@ var ApprovalCoordinator = class {
|
|
|
3033
3400
|
return null;
|
|
3034
3401
|
}
|
|
3035
3402
|
}
|
|
3036
|
-
/** 设备上按了允许/拒绝(经服务端下发,或重连后 sync
|
|
3037
|
-
onDecision(approvalId, decision, deviceName = "") {
|
|
3403
|
+
/** 设备上按了允许/拒绝(经服务端下发,或重连后 sync 补到)。选择题的答案在 answers 里(下标) */
|
|
3404
|
+
onDecision(approvalId, decision, deviceName = "", answers) {
|
|
3038
3405
|
const rec = this.#d.store.get(approvalId);
|
|
3039
3406
|
if (!rec) {
|
|
3040
3407
|
this.#d.log(`\u6536\u5230\u672A\u77E5\u6388\u6743 ${approvalId} \u7684\u51B3\u5B9A\uFF0C\u5FFD\u7565`);
|
|
3041
3408
|
return;
|
|
3042
3409
|
}
|
|
3043
3410
|
if (rec.status !== "pending") return;
|
|
3411
|
+
if (rec.kind === "question") {
|
|
3412
|
+
this.#onQuestionDecision(rec, decision, deviceName, answers);
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3044
3415
|
const updates = rememberUpdatesOf(rec);
|
|
3045
3416
|
const always = decision === "allow_always" && rec.mode === "window" && updates !== null;
|
|
3046
3417
|
if (decision === "allow_always") decision = "allow";
|
|
@@ -3054,10 +3425,12 @@ var ApprovalCoordinator = class {
|
|
|
3054
3425
|
this.#d.store.save();
|
|
3055
3426
|
if (rec.mode === "window") {
|
|
3056
3427
|
const notice = windowNotice(decision, rec.summary, deviceName) + (always ? "\uFF08\u5DF2\u8BB0\u4F4F\uFF09" : "");
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3428
|
+
if (!NO_NOTICE_TOOLS.has(rec.tool)) {
|
|
3429
|
+
try {
|
|
3430
|
+
;
|
|
3431
|
+
(this.#d.writeNotice ?? writeNotice)(rec.sessionId, notice);
|
|
3432
|
+
} catch {
|
|
3433
|
+
}
|
|
3061
3434
|
}
|
|
3062
3435
|
waiter(
|
|
3063
3436
|
decision === "allow" ? { decision: "allow", notice, ...always && updates ? { updatedPermissions: updates } : {} } : (
|
|
@@ -3085,6 +3458,16 @@ var ApprovalCoordinator = class {
|
|
|
3085
3458
|
this.#d.store.save();
|
|
3086
3459
|
if (decision === "allow") this.#d.resume(rec, "orphan");
|
|
3087
3460
|
}
|
|
3461
|
+
/**
|
|
3462
|
+
* 认出某个后台进程其实在跑 sessionId 这个会话(它还没进过关卡、run 上没记会话号,例如按进程号认出来的新建任务):
|
|
3463
|
+
* 记上会话号并标成已被取代 —— 之后它再进关卡立即被拒,不会在 hold 上挂住、让排在它后面的回复等 7 天
|
|
3464
|
+
*/
|
|
3465
|
+
supersedeRun(runId, sessionId) {
|
|
3466
|
+
const run2 = this.#runs.get(runId);
|
|
3467
|
+
if (!run2) return;
|
|
3468
|
+
if (!run2.sessionId) run2.sessionId = sessionId;
|
|
3469
|
+
run2.superseded = true;
|
|
3470
|
+
}
|
|
3088
3471
|
/**
|
|
3089
3472
|
* 同一会话来了新指令:等待中的那一步按"没批准"处理(spec E9,和窗口里弹框时直接打字一样)。
|
|
3090
3473
|
* 🔴 必须在新指令【入队之前】调:hold 模式下旧进程还占着这个会话的串行队列,
|
|
@@ -3167,7 +3550,7 @@ var ApprovalCoordinator = class {
|
|
|
3167
3550
|
const got = await this.#d.backend.sync(ids.slice(i, i + 50));
|
|
3168
3551
|
for (const g of got) {
|
|
3169
3552
|
if (g.decision === "cancelled") this.#markCancelled(g.approvalId);
|
|
3170
|
-
else this.onDecision(g.approvalId, g.decision, g.deviceName);
|
|
3553
|
+
else this.onDecision(g.approvalId, g.decision, g.deviceName, g.answers);
|
|
3171
3554
|
}
|
|
3172
3555
|
} catch (e) {
|
|
3173
3556
|
this.#d.log(`\u540C\u6B65\u6388\u6743\u72B6\u6001\u5931\u8D25\uFF1A${e.message}`);
|
|
@@ -3258,13 +3641,15 @@ function tokenEquals(a, b) {
|
|
|
3258
3641
|
}
|
|
3259
3642
|
var GateServer = class {
|
|
3260
3643
|
#handler;
|
|
3644
|
+
#notify;
|
|
3261
3645
|
#token = randomBytes3(32).toString("hex");
|
|
3262
3646
|
#server = null;
|
|
3263
3647
|
#inflight = 0;
|
|
3264
3648
|
/** 自动升级排空中:所有请求直接 503(远程 gate → exit 2 拦下;窗口 gate → 退回窗口里本地确认) */
|
|
3265
3649
|
refusing = false;
|
|
3266
|
-
constructor(handler) {
|
|
3650
|
+
constructor(handler, notify = null) {
|
|
3267
3651
|
this.#handler = handler;
|
|
3652
|
+
this.#notify = notify;
|
|
3268
3653
|
}
|
|
3269
3654
|
get token() {
|
|
3270
3655
|
return this.#token;
|
|
@@ -3276,9 +3661,9 @@ var GateServer = class {
|
|
|
3276
3661
|
/** 起监听并写 gate.json。返回实际端口 */
|
|
3277
3662
|
async start() {
|
|
3278
3663
|
const server = createServer((req2, res) => void this.#serve(req2, res));
|
|
3279
|
-
await new Promise((
|
|
3664
|
+
await new Promise((resolve7, reject) => {
|
|
3280
3665
|
server.once("error", reject);
|
|
3281
|
-
server.listen(0, "127.0.0.1", () =>
|
|
3666
|
+
server.listen(0, "127.0.0.1", () => resolve7());
|
|
3282
3667
|
});
|
|
3283
3668
|
this.#server = server;
|
|
3284
3669
|
const addr = server.address();
|
|
@@ -3299,7 +3684,7 @@ var GateServer = class {
|
|
|
3299
3684
|
}
|
|
3300
3685
|
const s = this.#server;
|
|
3301
3686
|
this.#server = null;
|
|
3302
|
-
if (s) await new Promise((
|
|
3687
|
+
if (s) await new Promise((resolve7) => s.close(() => resolve7()));
|
|
3303
3688
|
}
|
|
3304
3689
|
async #serve(req2, res) {
|
|
3305
3690
|
const reply = (status, body2) => {
|
|
@@ -3329,6 +3714,14 @@ var GateServer = class {
|
|
|
3329
3714
|
return reply(400);
|
|
3330
3715
|
}
|
|
3331
3716
|
if (!parsed.hook || typeof parsed.hook !== "object") return reply(400);
|
|
3717
|
+
if (parsed.mode === "notify") {
|
|
3718
|
+
if (typeof parsed.tool !== "string" || !/^[a-z_]{1,32}$/.test(parsed.tool)) return reply(400);
|
|
3719
|
+
try {
|
|
3720
|
+
this.#notify?.({ tool: parsed.tool, hook: parsed.hook });
|
|
3721
|
+
} catch {
|
|
3722
|
+
}
|
|
3723
|
+
return reply(200, { decision: "none" });
|
|
3724
|
+
}
|
|
3332
3725
|
const window = parsed.mode === "window";
|
|
3333
3726
|
if (!window && typeof parsed.runId !== "string") return reply(400);
|
|
3334
3727
|
const ac = new AbortController();
|
|
@@ -3411,7 +3804,7 @@ function buildHandshakeUrl(cfg, opts = {}) {
|
|
|
3411
3804
|
}
|
|
3412
3805
|
|
|
3413
3806
|
// src/daemon/log.ts
|
|
3414
|
-
import { appendFileSync, mkdirSync as mkdirSync12, statSync as
|
|
3807
|
+
import { appendFileSync, mkdirSync as mkdirSync12, statSync as statSync5, truncateSync } from "fs";
|
|
3415
3808
|
import { join as join16 } from "path";
|
|
3416
3809
|
var MAX_LOG_BYTES = 2 * 1024 * 1024;
|
|
3417
3810
|
function logPath() {
|
|
@@ -3419,7 +3812,7 @@ function logPath() {
|
|
|
3419
3812
|
}
|
|
3420
3813
|
function rotateIfNeeded(path) {
|
|
3421
3814
|
try {
|
|
3422
|
-
if (
|
|
3815
|
+
if (statSync5(path).size > MAX_LOG_BYTES) truncateSync(path, 0);
|
|
3423
3816
|
} catch {
|
|
3424
3817
|
}
|
|
3425
3818
|
}
|
|
@@ -3468,118 +3861,1375 @@ var SessionQueue = class {
|
|
|
3468
3861
|
});
|
|
3469
3862
|
return next;
|
|
3470
3863
|
}
|
|
3471
|
-
/** 这个会话有没有在跑 / 在排队的任务 */
|
|
3472
|
-
busy(sessionId) {
|
|
3473
|
-
return this.#tails.has(sessionId || "");
|
|
3864
|
+
/** 这个会话有没有在跑 / 在排队的任务 */
|
|
3865
|
+
busy(sessionId) {
|
|
3866
|
+
return this.#tails.has(sessionId || "");
|
|
3867
|
+
}
|
|
3868
|
+
/** 当前有多少个会话still有在跑/排队的任务。只给测试和诊断用 */
|
|
3869
|
+
get activeSessions() {
|
|
3870
|
+
return this.#tails.size;
|
|
3871
|
+
}
|
|
3872
|
+
/** 等所有会话都排空。只给测试和优雅退出用 */
|
|
3873
|
+
async drain() {
|
|
3874
|
+
while (this.#tails.size > 0) {
|
|
3875
|
+
await Promise.all([...this.#tails.values()]);
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
};
|
|
3879
|
+
|
|
3880
|
+
// src/daemon/resolve.ts
|
|
3881
|
+
function resolveTarget(cmd, deps) {
|
|
3882
|
+
if (cmd.sessionId) {
|
|
3883
|
+
const sessionId = deps.alias ? deps.alias(cmd.sessionId) : cmd.sessionId;
|
|
3884
|
+
const state = deps.readState(sessionId);
|
|
3885
|
+
if (!state) return { ok: false, reason: "session_gone" };
|
|
3886
|
+
if (!state.cwd) return { ok: false, reason: "no_cwd" };
|
|
3887
|
+
if (deps.windowOpen?.(state)) return { ok: false, reason: "window_open" };
|
|
3888
|
+
return { ok: true, sessionId, cwd: state.cwd };
|
|
3889
|
+
}
|
|
3890
|
+
let dirCwd = "";
|
|
3891
|
+
if (cmd.dirRef) {
|
|
3892
|
+
dirCwd = deps.dirOf ? deps.dirOf(cmd.dirRef) : "";
|
|
3893
|
+
if (!dirCwd) return { ok: false, reason: "dir_gone" };
|
|
3894
|
+
}
|
|
3895
|
+
const cwd = dirCwd || deps.remoteCwd();
|
|
3896
|
+
if (!cwd) return { ok: false, reason: "no_remote_cwd" };
|
|
3897
|
+
return { ok: true, sessionId: "", cwd };
|
|
3898
|
+
}
|
|
3899
|
+
function failureMessage(reason) {
|
|
3900
|
+
switch (reason) {
|
|
3901
|
+
case "session_gone":
|
|
3902
|
+
return "\u8FD9\u4E2A\u4F1A\u8BDD\u5DF2\u7ECF\u4E0D\u5728\u4E86";
|
|
3903
|
+
case "no_cwd":
|
|
3904
|
+
return "\u8FD9\u4E2A\u4F1A\u8BDD\u9700\u8981\u66F4\u65B0 DeskBuddy \u540E\u624D\u80FD\u8FDC\u7A0B\u64CD\u4F5C";
|
|
3905
|
+
case "no_remote_cwd":
|
|
3906
|
+
return "\u8FD8\u6CA1\u8BBE\u7F6E\u8FDC\u7A0B\u4EFB\u52A1\u7684\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u8BF7\u5728\u7535\u8111\u4E0A\u8DD1\u4E00\u6B21 deskbuddy setup";
|
|
3907
|
+
case "window_open":
|
|
3908
|
+
return "\u8FD9\u4E2A\u4EFB\u52A1\u6B63\u5728\u7535\u8111\u4E0A\u6253\u5F00\uFF0C\u8BF7\u5728\u7535\u8111\u4E0A\u7EE7\u7EED\uFF0C\u6216\u65B0\u5EFA\u4EFB\u52A1";
|
|
3909
|
+
case "dir_gone":
|
|
3910
|
+
return "\u7535\u8111\u4E0A\u627E\u4E0D\u5230\u8FD9\u4E2A\u76EE\u5F55\u4E86\uFF0C\u8BF7\u6362\u4E00\u4E2A\u76EE\u5F55";
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
// src/daemon/seen.ts
|
|
3915
|
+
import { mkdirSync as mkdirSync13, readFileSync as readFileSync12, renameSync as renameSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
3916
|
+
import { join as join17 } from "path";
|
|
3917
|
+
var SEEN_CAPACITY = 200;
|
|
3918
|
+
function seenPath() {
|
|
3919
|
+
return join17(deskbuddyHome(), "seen.json");
|
|
3920
|
+
}
|
|
3921
|
+
var SeenIds = class _SeenIds {
|
|
3922
|
+
#ids = [];
|
|
3923
|
+
#set = /* @__PURE__ */ new Set();
|
|
3924
|
+
constructor(initial = []) {
|
|
3925
|
+
for (const id of initial) this.#push(id);
|
|
3926
|
+
}
|
|
3927
|
+
#push(id) {
|
|
3928
|
+
if (this.#set.has(id)) return;
|
|
3929
|
+
this.#ids.push(id);
|
|
3930
|
+
this.#set.add(id);
|
|
3931
|
+
while (this.#ids.length > SEEN_CAPACITY) {
|
|
3932
|
+
const dropped = this.#ids.shift();
|
|
3933
|
+
if (dropped !== void 0) this.#set.delete(dropped);
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
has(id) {
|
|
3937
|
+
return this.#set.has(id);
|
|
3938
|
+
}
|
|
3939
|
+
/** 记下来。返回 true 表示这是**新的**(之前没见过) */
|
|
3940
|
+
remember(id) {
|
|
3941
|
+
if (this.#set.has(id)) return false;
|
|
3942
|
+
this.#push(id);
|
|
3943
|
+
return true;
|
|
3944
|
+
}
|
|
3945
|
+
snapshot() {
|
|
3946
|
+
return [...this.#ids];
|
|
3947
|
+
}
|
|
3948
|
+
static load() {
|
|
3949
|
+
try {
|
|
3950
|
+
const raw = JSON.parse(readFileSync12(seenPath(), "utf8"));
|
|
3951
|
+
return new _SeenIds(Array.isArray(raw?.ids) ? raw.ids.filter((x) => typeof x === "string") : []);
|
|
3952
|
+
} catch {
|
|
3953
|
+
return new _SeenIds();
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
save() {
|
|
3957
|
+
try {
|
|
3958
|
+
mkdirSync13(deskbuddyHome(), { recursive: true });
|
|
3959
|
+
const tmp = seenPath() + ".tmp";
|
|
3960
|
+
writeFileSync11(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
|
|
3961
|
+
renameSync9(tmp, seenPath());
|
|
3962
|
+
} catch {
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
};
|
|
3966
|
+
|
|
3967
|
+
// src/daemon/qwenworkBridge.ts
|
|
3968
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
3969
|
+
import { homedir as homedir7 } from "os";
|
|
3970
|
+
import { basename as basename7, dirname as dirname9, join as join18, resolve as resolve3 } from "path";
|
|
3971
|
+
var SUBMIT_TOOL = "qwenwork_task_submit_response";
|
|
3972
|
+
var DETAIL_TOOL = "qwenwork_task_get_detail";
|
|
3973
|
+
var POLL_MS = 2e3;
|
|
3974
|
+
var REQUEST_TIMEOUT_MS = 5e3;
|
|
3975
|
+
var EARLY_WINDOW_MS = 10 * 1e3;
|
|
3976
|
+
var EARLY_RETRY_MS = 1e3;
|
|
3977
|
+
var EARLY_RETRIES = 3;
|
|
3978
|
+
var SEEN_TTL_MS = 60 * 60 * 1e3;
|
|
3979
|
+
var DENY_MESSAGE = "\u7528\u6237\u5728 DeskBuddy \u8BBE\u5907\u4E0A\u62D2\u7EDD\u4E86\u8FD9\u4E00\u6B65\uFF0C\u4E0D\u8981\u91CD\u8BD5\u8FD9\u4E2A\u64CD\u4F5C";
|
|
3980
|
+
var TERMINAL_STATUS = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "canceled", "error", "stopped", "aborted"]);
|
|
3981
|
+
var PENDING_STATES = /* @__PURE__ */ new Set(["call", "approval-requested", "awaiting-approval", "waiting-for-confirmation", "pending-confirmation"]);
|
|
3982
|
+
var DETAIL_LIMIT = 5;
|
|
3983
|
+
var NO_ID_DEDUPE_MS = 5 * 1e3;
|
|
3984
|
+
var TOOL_DONE_STATES = /* @__PURE__ */ new Set(["output-available", "output-error", "output-denied", "result", "error", "denied", "rejected"]);
|
|
3985
|
+
function isResolvedElsewhere(text) {
|
|
3986
|
+
return /already been resolved|no pending/i.test(text);
|
|
3987
|
+
}
|
|
3988
|
+
function isLoopbackHttp(url) {
|
|
3989
|
+
try {
|
|
3990
|
+
const u = new URL(url);
|
|
3991
|
+
return (u.protocol === "http:" || u.protocol === "https:") && ["127.0.0.1", "localhost", "[::1]"].includes(u.hostname);
|
|
3992
|
+
} catch {
|
|
3993
|
+
return false;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
function parseMcpConf(raw) {
|
|
3997
|
+
if (!raw) return null;
|
|
3998
|
+
let c;
|
|
3999
|
+
try {
|
|
4000
|
+
c = JSON.parse(raw);
|
|
4001
|
+
} catch {
|
|
4002
|
+
return null;
|
|
4003
|
+
}
|
|
4004
|
+
const url = typeof c.url === "string" ? c.url : "";
|
|
4005
|
+
if (!url || !isLoopbackHttp(url)) return null;
|
|
4006
|
+
const h = c.headers && typeof c.headers === "object" ? c.headers : {};
|
|
4007
|
+
const key = typeof h["x-api-key"] === "string" && h["x-api-key"] ? h["x-api-key"] : typeof c.token === "string" ? c.token : "";
|
|
4008
|
+
if (!key) return null;
|
|
4009
|
+
return { url, key };
|
|
4010
|
+
}
|
|
4011
|
+
function sameDir(a, b) {
|
|
4012
|
+
return process.platform === "linux" ? a === b : a.toLowerCase() === b.toLowerCase();
|
|
4013
|
+
}
|
|
4014
|
+
function chatIdOf(cwd, root) {
|
|
4015
|
+
if (typeof cwd !== "string" || !cwd) return null;
|
|
4016
|
+
const dir = resolve3(cwd);
|
|
4017
|
+
if (!sameDir(dirname9(dir), resolve3(root))) return null;
|
|
4018
|
+
const id = basename7(dir);
|
|
4019
|
+
return /^[A-Za-z0-9_-]{1,64}$/.test(id) ? id : null;
|
|
4020
|
+
}
|
|
4021
|
+
function submitAnswers(toolInput, lists) {
|
|
4022
|
+
const qs = toolInput?.questions;
|
|
4023
|
+
if (!Array.isArray(qs) || !Array.isArray(lists) || lists.length !== qs.length) return null;
|
|
4024
|
+
const out = {};
|
|
4025
|
+
for (let i = 0; i < qs.length; i++) {
|
|
4026
|
+
const q = qs[i] ?? {};
|
|
4027
|
+
const items = lists[i];
|
|
4028
|
+
if (typeof q.question !== "string" || !q.question || !Array.isArray(items) || items.length === 0) return null;
|
|
4029
|
+
if (!items.every((x) => typeof x === "string")) return null;
|
|
4030
|
+
out[q.question] = items.join(", ");
|
|
4031
|
+
}
|
|
4032
|
+
return out;
|
|
4033
|
+
}
|
|
4034
|
+
function parseRpcBody(text, contentType) {
|
|
4035
|
+
const tryJson = (s) => {
|
|
4036
|
+
try {
|
|
4037
|
+
const v = JSON.parse(s);
|
|
4038
|
+
return v && typeof v === "object" ? v : null;
|
|
4039
|
+
} catch {
|
|
4040
|
+
return null;
|
|
4041
|
+
}
|
|
4042
|
+
};
|
|
4043
|
+
if (!/event-stream/i.test(contentType)) return tryJson(text);
|
|
4044
|
+
let last = null;
|
|
4045
|
+
for (const line of text.split("\n")) {
|
|
4046
|
+
if (!line.startsWith("data:")) continue;
|
|
4047
|
+
const v = tryJson(line.slice(5).trim());
|
|
4048
|
+
if (v && (v.result !== void 0 || v.error !== void 0)) last = v;
|
|
4049
|
+
}
|
|
4050
|
+
return last;
|
|
4051
|
+
}
|
|
4052
|
+
var QwenWorkMcp = class {
|
|
4053
|
+
#read;
|
|
4054
|
+
#fetch;
|
|
4055
|
+
#log;
|
|
4056
|
+
/** 已核对过工具清单的那个 url + key */
|
|
4057
|
+
#checked = null;
|
|
4058
|
+
#session = "";
|
|
4059
|
+
#nextId = 1;
|
|
4060
|
+
constructor(read, fetchFn, log2) {
|
|
4061
|
+
this.#read = read;
|
|
4062
|
+
this.#fetch = fetchFn;
|
|
4063
|
+
this.#log = log2;
|
|
4064
|
+
}
|
|
4065
|
+
async #rpc(conf, method, params, notification = false) {
|
|
4066
|
+
const headers = {
|
|
4067
|
+
"content-type": "application/json",
|
|
4068
|
+
accept: "application/json, text/event-stream",
|
|
4069
|
+
"x-api-key": conf.key
|
|
4070
|
+
};
|
|
4071
|
+
if (this.#session) headers["mcp-session-id"] = this.#session;
|
|
4072
|
+
const msg = { jsonrpc: "2.0", method, ...params ? { params } : {} };
|
|
4073
|
+
if (!notification) msg.id = this.#nextId++;
|
|
4074
|
+
const ac = new AbortController();
|
|
4075
|
+
const timer = setTimeout(() => ac.abort(), REQUEST_TIMEOUT_MS);
|
|
4076
|
+
timer.unref?.();
|
|
4077
|
+
try {
|
|
4078
|
+
const r = await this.#fetch(conf.url, { method: "POST", headers, body: JSON.stringify(msg), signal: ac.signal });
|
|
4079
|
+
const sid = r.headers.get("mcp-session-id");
|
|
4080
|
+
if (sid) this.#session = sid;
|
|
4081
|
+
const text = await r.text();
|
|
4082
|
+
if (r.status === 202 && notification) return { ok: true, body: null };
|
|
4083
|
+
if (r.status < 200 || r.status >= 300) return { ok: false, message: `HTTP ${r.status}` };
|
|
4084
|
+
if (notification) return { ok: true, body: null };
|
|
4085
|
+
return { ok: true, body: parseRpcBody(text, r.headers.get("content-type") ?? "") };
|
|
4086
|
+
} catch (e) {
|
|
4087
|
+
return { ok: false, message: e?.name === "AbortError" ? "timeout" : String(e?.message ?? e) };
|
|
4088
|
+
} finally {
|
|
4089
|
+
clearTimeout(timer);
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
/** 在途的握手(同一 url + key 只握一次) */
|
|
4093
|
+
#handshake = null;
|
|
4094
|
+
/** 连得上且有 submit_response。返回 config(给后续调用用),不行返回失败原因 */
|
|
4095
|
+
async ensure() {
|
|
4096
|
+
const conf = parseMcpConf(this.#read());
|
|
4097
|
+
if (!conf) return { ok: false, why: "no_config", message: "\u8BFB\u4E0D\u5230\u5343\u95EE\u529E\u516C\u7684 MCP \u914D\u7F6E\uFF08\u6216\u5730\u5740\u4E0D\u662F\u672C\u673A\u56DE\u73AF\uFF09" };
|
|
4098
|
+
const c = this.#checked;
|
|
4099
|
+
if (c && c.url === conf.url && c.key === conf.key) {
|
|
4100
|
+
return c.ok ? { ok: true, conf } : { ok: false, why: "unsupported", message: `\u5343\u95EE\u529E\u516C MCP \u6CA1\u6709 ${SUBMIT_TOOL}` };
|
|
4101
|
+
}
|
|
4102
|
+
const h = this.#handshake;
|
|
4103
|
+
if (h && h.url === conf.url && h.key === conf.key) return h.p;
|
|
4104
|
+
const p = this.#doHandshake(conf);
|
|
4105
|
+
this.#handshake = { url: conf.url, key: conf.key, p };
|
|
4106
|
+
try {
|
|
4107
|
+
return await p;
|
|
4108
|
+
} finally {
|
|
4109
|
+
if (this.#handshake?.p === p) this.#handshake = null;
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
async #doHandshake(conf) {
|
|
4113
|
+
this.#session = "";
|
|
4114
|
+
const init = await this.#rpc(conf, "initialize", {
|
|
4115
|
+
protocolVersion: "2025-03-26",
|
|
4116
|
+
capabilities: {},
|
|
4117
|
+
clientInfo: { name: "deskbuddy", version: "1" }
|
|
4118
|
+
});
|
|
4119
|
+
if (!init.ok) return { ok: false, why: "network", message: `\u8FDE\u4E0D\u4E0A\u5343\u95EE\u529E\u516C MCP\uFF1A${init.message}` };
|
|
4120
|
+
if (!init.body || init.body.error) return { ok: false, why: "network", message: "\u5343\u95EE\u529E\u516C MCP \u521D\u59CB\u5316\u5931\u8D25" };
|
|
4121
|
+
await this.#rpc(conf, "notifications/initialized", void 0, true);
|
|
4122
|
+
const list = await this.#rpc(conf, "tools/list", {});
|
|
4123
|
+
if (!list.ok) return { ok: false, why: "network", message: `\u5343\u95EE\u529E\u516C MCP \u5DE5\u5177\u6E05\u5355\u53D6\u4E0D\u5230\uFF1A${list.message}` };
|
|
4124
|
+
const tools = list.body?.result?.tools ?? [];
|
|
4125
|
+
const ok = Array.isArray(tools) && tools.some((t) => t?.name === SUBMIT_TOOL);
|
|
4126
|
+
this.#checked = { url: conf.url, key: conf.key, ok };
|
|
4127
|
+
if (!ok) {
|
|
4128
|
+
this.#log(`\u5343\u95EE\u529E\u516C MCP \u6CA1\u6709 ${SUBMIT_TOOL}\uFF0C\u9009\u62E9\u9898 / \u6388\u6743\u4E0D\u63A8\u8BBE\u5907\uFF0C\u53EA\u4E0A\u62A5\u72B6\u6001`);
|
|
4129
|
+
return { ok: false, why: "unsupported", message: `\u5343\u95EE\u529E\u516C MCP \u6CA1\u6709 ${SUBMIT_TOOL}` };
|
|
4130
|
+
}
|
|
4131
|
+
return { ok: true, conf };
|
|
4132
|
+
}
|
|
4133
|
+
async call(name, args) {
|
|
4134
|
+
const e = await this.ensure();
|
|
4135
|
+
if (!e.ok) return e;
|
|
4136
|
+
const r = await this.#rpc(e.conf, "tools/call", { name, arguments: args });
|
|
4137
|
+
if (!r.ok) {
|
|
4138
|
+
this.#checked = null;
|
|
4139
|
+
return { ok: false, why: "network", message: r.message };
|
|
4140
|
+
}
|
|
4141
|
+
const body = r.body;
|
|
4142
|
+
if (!body) return { ok: false, why: "rpc", message: "\u56DE\u8BDD\u770B\u4E0D\u61C2" };
|
|
4143
|
+
if (body.error) {
|
|
4144
|
+
const m = body.error?.message;
|
|
4145
|
+
this.#checked = null;
|
|
4146
|
+
return { ok: false, why: "rpc", message: typeof m === "string" ? m : "error" };
|
|
4147
|
+
}
|
|
4148
|
+
const res = body.result ?? {};
|
|
4149
|
+
const content = Array.isArray(res.content) ? res.content : [];
|
|
4150
|
+
const text = content.map((c) => typeof c?.text === "string" ? c.text : "").join("\n");
|
|
4151
|
+
return { ok: true, text, isError: res.isError === true };
|
|
4152
|
+
}
|
|
4153
|
+
};
|
|
4154
|
+
var QwenWorkBridge = class {
|
|
4155
|
+
#d;
|
|
4156
|
+
#mcp;
|
|
4157
|
+
/** chatId → 正在等设备的那张卡(同一 chatId 同时只推一张) */
|
|
4158
|
+
#cards = /* @__PURE__ */ new Map();
|
|
4159
|
+
/** 推过的 (chatId, toolUseId) → 时间:同一道题 / 同一个授权框只推一次 */
|
|
4160
|
+
#seen = /* @__PURE__ */ new Map();
|
|
4161
|
+
/** 没带 toolUseId 的推送(只为日志限频) */
|
|
4162
|
+
#noIdLogged = /* @__PURE__ */ new Map();
|
|
4163
|
+
#timer = null;
|
|
4164
|
+
#polling = false;
|
|
4165
|
+
#stopped = false;
|
|
4166
|
+
constructor(deps) {
|
|
4167
|
+
this.#d = deps;
|
|
4168
|
+
const read = deps.readConfig ?? (() => readConfigFile(join18(qwenworkHome(), "mcp-adaptor.config")));
|
|
4169
|
+
const f = deps.fetch ?? globalThis.fetch;
|
|
4170
|
+
this.#mcp = new QwenWorkMcp(read, f, deps.log);
|
|
4171
|
+
}
|
|
4172
|
+
#now() {
|
|
4173
|
+
return this.#d.now ? this.#d.now() : Date.now();
|
|
4174
|
+
}
|
|
4175
|
+
/** 只给测试和诊断用 */
|
|
4176
|
+
get pending() {
|
|
4177
|
+
return this.#cards.size;
|
|
4178
|
+
}
|
|
4179
|
+
stop() {
|
|
4180
|
+
this.#stopped = true;
|
|
4181
|
+
if (this.#timer) clearInterval(this.#timer);
|
|
4182
|
+
this.#timer = null;
|
|
4183
|
+
this.#abortAll();
|
|
4184
|
+
}
|
|
4185
|
+
#abortAll() {
|
|
4186
|
+
for (const c of this.#cards.values()) c.ac.abort();
|
|
4187
|
+
this.#cards.clear();
|
|
4188
|
+
}
|
|
4189
|
+
/** hook 发来的 notify(GateServer mode=notify)。不抛错 */
|
|
4190
|
+
onNotify(req2) {
|
|
4191
|
+
void this.#onNotify(req2).catch((e) => this.#d.log(`\u5343\u95EE\u529E\u516C\u63A8\u5361\u51FA\u9519\uFF1A${e?.message ?? e}`));
|
|
4192
|
+
}
|
|
4193
|
+
async #onNotify(req2) {
|
|
4194
|
+
if (this.#stopped || req2.tool !== "qwenwork") return;
|
|
4195
|
+
if (this.#d.enabled && !this.#d.enabled()) return;
|
|
4196
|
+
const h = req2.hook;
|
|
4197
|
+
const event = h.hook_event_name;
|
|
4198
|
+
const sessionId = typeof h.session_id === "string" ? h.session_id : "";
|
|
4199
|
+
if (!sessionId) return;
|
|
4200
|
+
let kind;
|
|
4201
|
+
let toolName;
|
|
4202
|
+
let toolInput;
|
|
4203
|
+
let toolUseId;
|
|
4204
|
+
if (event === "PreToolUse" && h.tool_name === "AskUserQuestion") {
|
|
4205
|
+
kind = "question";
|
|
4206
|
+
toolName = "AskUserQuestion";
|
|
4207
|
+
toolInput = h.tool_input;
|
|
4208
|
+
toolUseId = typeof h.tool_use_id === "string" ? h.tool_use_id : "";
|
|
4209
|
+
} else if (event === "Notification" && h.notification_type === "permission_prompt") {
|
|
4210
|
+
const det = h.details && typeof h.details === "object" ? h.details : {};
|
|
4211
|
+
kind = "permission";
|
|
4212
|
+
toolName = typeof det.toolName === "string" ? det.toolName : "";
|
|
4213
|
+
toolInput = det.input && typeof det.input === "object" ? det.input : {};
|
|
4214
|
+
toolUseId = typeof det.toolUseId === "string" ? det.toolUseId : "";
|
|
4215
|
+
if (!toolName) return;
|
|
4216
|
+
} else return;
|
|
4217
|
+
const root = this.#d.workspaceRoot ?? join18(homedir7(), ".qwenworkcn", "workspace");
|
|
4218
|
+
const chatId = chatIdOf(h.cwd, root);
|
|
4219
|
+
if (!chatId) {
|
|
4220
|
+
this.#d.log("\u5343\u95EE\u529E\u516C\uFF1A\u8FD9\u9053\u9898 / \u6388\u6743\u4E0D\u5728\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF08\u53EF\u80FD\u662F\u5B50\u4EE3\u7406\uFF09\uFF0C\u4E0D\u63A8\u8BBE\u5907");
|
|
4221
|
+
return;
|
|
4222
|
+
}
|
|
4223
|
+
if (!toolUseId) {
|
|
4224
|
+
const fp = `${chatId}:${kind}`;
|
|
4225
|
+
const last = this.#noIdLogged.get(fp) ?? 0;
|
|
4226
|
+
if (this.#now() - last > NO_ID_DEDUPE_MS) this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u4EFB\u52A1 ${chatId.slice(0, 8)} \u7684${kind === "question" ? "\u9009\u62E9\u9898" : "\u6388\u6743"}\u6CA1\u5E26 toolUseId\uFF0C\u4E0D\u63A8\u8BBE\u5907`);
|
|
4227
|
+
this.#noIdLogged.set(fp, this.#now());
|
|
4228
|
+
return;
|
|
4229
|
+
}
|
|
4230
|
+
const key = `${chatId}:${toolUseId}`;
|
|
4231
|
+
this.#prune();
|
|
4232
|
+
if (this.#seen.has(key)) return;
|
|
4233
|
+
this.#seen.set(key, this.#now());
|
|
4234
|
+
this.#replaceOld(chatId, toolUseId);
|
|
4235
|
+
const e = await this.#mcp.ensure();
|
|
4236
|
+
if (!e.ok) {
|
|
4237
|
+
if (e.why !== "unsupported") this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A${e.message}\uFF0C\u8FD9\u4E00\u6761\u4E0D\u63A8\u8BBE\u5907`);
|
|
4238
|
+
return;
|
|
4239
|
+
}
|
|
4240
|
+
if (this.#stopped) return;
|
|
4241
|
+
this.#replaceOld(chatId, toolUseId);
|
|
4242
|
+
const card = { key, chatId, toolUseId, kind, toolInput, ac: new AbortController(), createdAt: this.#now() };
|
|
4243
|
+
this.#cards.set(chatId, card);
|
|
4244
|
+
this.#ensurePoll();
|
|
4245
|
+
const gateReq = {
|
|
4246
|
+
window: true,
|
|
4247
|
+
tool: "qwenwork",
|
|
4248
|
+
hook: {
|
|
4249
|
+
hook_event_name: "PermissionRequest",
|
|
4250
|
+
session_id: sessionId,
|
|
4251
|
+
cwd: h.cwd,
|
|
4252
|
+
tool_name: toolName,
|
|
4253
|
+
tool_input: toolInput,
|
|
4254
|
+
...kind === "question" && typeof h.transcript_path === "string" ? { transcript_path: h.transcript_path } : {}
|
|
4255
|
+
}
|
|
4256
|
+
};
|
|
4257
|
+
let reply;
|
|
4258
|
+
try {
|
|
4259
|
+
reply = await this.#d.gate(gateReq, card.ac.signal);
|
|
4260
|
+
} catch {
|
|
4261
|
+
if (this.#cards.get(chatId) === card) this.#cards.delete(chatId);
|
|
4262
|
+
return;
|
|
4263
|
+
}
|
|
4264
|
+
if (this.#cards.get(chatId) !== card || card.ac.signal.aborted) return;
|
|
4265
|
+
this.#cards.delete(chatId);
|
|
4266
|
+
await this.#submit(card, reply);
|
|
4267
|
+
}
|
|
4268
|
+
#replaceOld(chatId, toolUseId) {
|
|
4269
|
+
const old = this.#cards.get(chatId);
|
|
4270
|
+
if (!old || old.toolUseId === toolUseId) return;
|
|
4271
|
+
this.#cards.delete(chatId);
|
|
4272
|
+
this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u4EFB\u52A1 ${chatId.slice(0, 8)} \u51FA\u73B0\u65B0\u7684\u4E00\u6B65\uFF0C\u64A4\u6389\u65E7\u5361`);
|
|
4273
|
+
old.ac.abort();
|
|
4274
|
+
}
|
|
4275
|
+
/**
|
|
4276
|
+
* 提交前核对:界面上此刻在等的正是这张卡那一步(等待类状态的工具调用恰好一条、且 toolCallId 对得上)。
|
|
4277
|
+
* 对不上 / 判断不了一律返回原因(不提交)
|
|
4278
|
+
*/
|
|
4279
|
+
async #verifyPending(card) {
|
|
4280
|
+
const r = await this.#mcp.call(DETAIL_TOOL, { chatId: card.chatId, limit: DETAIL_LIMIT });
|
|
4281
|
+
if (!r.ok) return `\u67E5\u4E0D\u5230\u4EFB\u52A1\u8BE6\u60C5\uFF08${r.why}\uFF09`;
|
|
4282
|
+
return pendingMismatch(r.text, card.toolUseId);
|
|
4283
|
+
}
|
|
4284
|
+
async #submit(card, reply) {
|
|
4285
|
+
let args = null;
|
|
4286
|
+
if (card.kind === "question") {
|
|
4287
|
+
if (reply.decision !== "allow") return;
|
|
4288
|
+
const answers = submitAnswers(card.toolInput, reply.answerLists);
|
|
4289
|
+
if (!answers) {
|
|
4290
|
+
this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u4EFB\u52A1 ${card.chatId.slice(0, 8)} \u7684\u8BBE\u5907\u7B54\u6848\u62FC\u4E0D\u51FA\u6765\uFF0C\u754C\u9762\u7559\u7ED9\u4EBA\u7B54`);
|
|
4291
|
+
return;
|
|
4292
|
+
}
|
|
4293
|
+
args = { chatId: card.chatId, action: "answer", answers };
|
|
4294
|
+
} else if (reply.decision === "allow") {
|
|
4295
|
+
args = { chatId: card.chatId, action: "approve" };
|
|
4296
|
+
} else if (reply.decision === "deny") {
|
|
4297
|
+
args = { chatId: card.chatId, action: "deny", message: DENY_MESSAGE };
|
|
4298
|
+
} else return;
|
|
4299
|
+
if (!card.toolUseId) return;
|
|
4300
|
+
for (let attempt = 0; ; attempt++) {
|
|
4301
|
+
const why = await this.#verifyPending(card);
|
|
4302
|
+
if (why) {
|
|
4303
|
+
const early = this.#now() - card.createdAt < EARLY_WINDOW_MS && attempt < EARLY_RETRIES && /没有在等/.test(why);
|
|
4304
|
+
if (early) {
|
|
4305
|
+
await sleep(this.#d.retryMs ?? EARLY_RETRY_MS);
|
|
4306
|
+
continue;
|
|
4307
|
+
}
|
|
4308
|
+
this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u4EFB\u52A1 ${card.chatId.slice(0, 8)} ${why}\uFF0C\u8BBE\u5907\u7B54\u590D\u4E0D\u56DE\u586B\uFF08\u6309\u7535\u8111\u5DF2\u5904\u7406\uFF09`);
|
|
4309
|
+
return;
|
|
4310
|
+
}
|
|
4311
|
+
const r = await this.#mcp.call(SUBMIT_TOOL, args);
|
|
4312
|
+
if (!r.ok) {
|
|
4313
|
+
this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u56DE\u586B\u4EFB\u52A1 ${card.chatId.slice(0, 8)} \u5931\u8D25\uFF08${r.why}${r.message ? " " + r.message.slice(0, 80) : ""}\uFF09\uFF0C\u754C\u9762\u7559\u7ED9\u4EBA\u7B54`);
|
|
4314
|
+
return;
|
|
4315
|
+
}
|
|
4316
|
+
if (isResolvedElsewhere(r.text)) {
|
|
4317
|
+
const early = /no pending/i.test(r.text) && this.#now() - card.createdAt < EARLY_WINDOW_MS && attempt < EARLY_RETRIES;
|
|
4318
|
+
if (early) {
|
|
4319
|
+
await sleep(this.#d.retryMs ?? EARLY_RETRY_MS);
|
|
4320
|
+
continue;
|
|
4321
|
+
}
|
|
4322
|
+
this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u4EFB\u52A1 ${card.chatId.slice(0, 8)} \u7535\u8111\u4E0A\u5DF2\u5904\u7406\uFF0C\u8BBE\u5907\u7B54\u590D\u4E0D\u518D\u56DE\u586B`);
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
4325
|
+
if (r.isError) {
|
|
4326
|
+
this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u56DE\u586B\u4EFB\u52A1 ${card.chatId.slice(0, 8)} \u88AB\u62D2\uFF1A${r.text.slice(0, 80)}`);
|
|
4327
|
+
return;
|
|
4328
|
+
}
|
|
4329
|
+
this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u4EFB\u52A1 ${card.chatId.slice(0, 8)} \u5DF2\u6309\u8BBE\u5907\u7B54\u590D\u56DE\u586B\uFF08${card.kind}\uFF09`);
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
#prune() {
|
|
4334
|
+
const now = this.#now();
|
|
4335
|
+
for (const [k, at] of this.#seen) if (now - at > SEEN_TTL_MS) this.#seen.delete(k);
|
|
4336
|
+
for (const [k, at] of this.#noIdLogged) if (now - at > NO_ID_DEDUPE_MS) this.#noIdLogged.delete(k);
|
|
4337
|
+
}
|
|
4338
|
+
#ensurePoll() {
|
|
4339
|
+
if (this.#timer || this.#stopped) return;
|
|
4340
|
+
this.#timer = setInterval(() => void this.poll(), this.#d.pollMs ?? POLL_MS);
|
|
4341
|
+
this.#timer.unref?.();
|
|
4342
|
+
}
|
|
4343
|
+
/**
|
|
4344
|
+
* 待答期间查 get_detail:任务已不在跑、或这一步已经有结果(电脑上答过 / 批过 / 拒过)就撤卡。
|
|
4345
|
+
* 看不出来就不动(最迟靠 coordinator.scanWindows 在 PostToolUse / Stop 时撤)。MCP 连不上 → 全部撤卡
|
|
4346
|
+
*/
|
|
4347
|
+
async poll() {
|
|
4348
|
+
if (this.#polling) return;
|
|
4349
|
+
if (this.#cards.size === 0) {
|
|
4350
|
+
if (this.#timer) clearInterval(this.#timer);
|
|
4351
|
+
this.#timer = null;
|
|
4352
|
+
return;
|
|
4353
|
+
}
|
|
4354
|
+
this.#polling = true;
|
|
4355
|
+
try {
|
|
4356
|
+
for (const card of [...this.#cards.values()]) {
|
|
4357
|
+
if (this.#cards.get(card.chatId) !== card) continue;
|
|
4358
|
+
const r = await this.#mcp.call(DETAIL_TOOL, { chatId: card.chatId, limit: DETAIL_LIMIT });
|
|
4359
|
+
if (!r.ok) {
|
|
4360
|
+
if (r.why === "network" || r.why === "no_config") {
|
|
4361
|
+
this.#d.log(`\u5343\u95EE\u529E\u516C MCP \u8FDE\u4E0D\u4E0A\uFF08${r.message.slice(0, 80)}\uFF09\uFF0C\u64A4\u6389\u8BBE\u5907\u4E0A\u7B49\u7740\u7684\u5361`);
|
|
4362
|
+
this.#abortAll();
|
|
4363
|
+
return;
|
|
4364
|
+
}
|
|
4365
|
+
continue;
|
|
4366
|
+
}
|
|
4367
|
+
if (this.#cards.get(card.chatId) !== card) continue;
|
|
4368
|
+
const settled = detailSettled(r.text, card.toolUseId);
|
|
4369
|
+
if (settled) {
|
|
4370
|
+
this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u4EFB\u52A1 ${card.chatId.slice(0, 8)} ${settled}\uFF0C\u64A4\u5361`);
|
|
4371
|
+
this.#cards.delete(card.chatId);
|
|
4372
|
+
card.ac.abort();
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
} finally {
|
|
4376
|
+
this.#polling = false;
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
};
|
|
4380
|
+
function toolCallsOf(d) {
|
|
4381
|
+
const msgs = Array.isArray(d.messages) ? d.messages : [];
|
|
4382
|
+
return msgs.flatMap((m) => Array.isArray(m?.toolCalls) ? m.toolCalls : []);
|
|
4383
|
+
}
|
|
4384
|
+
function pendingIds(d) {
|
|
4385
|
+
return toolCallsOf(d).filter((c) => typeof c?.state === "string" && PENDING_STATES.has(c.state)).map((c) => typeof c.toolCallId === "string" ? c.toolCallId : "");
|
|
4386
|
+
}
|
|
4387
|
+
function pendingMismatch(text, toolUseId) {
|
|
4388
|
+
if (!toolUseId) return "\u6CA1\u6709 toolUseId\uFF0C\u6838\u5BF9\u4E0D\u4E86";
|
|
4389
|
+
let d;
|
|
4390
|
+
try {
|
|
4391
|
+
d = JSON.parse(text);
|
|
4392
|
+
} catch {
|
|
4393
|
+
return "\u4EFB\u52A1\u8BE6\u60C5\u770B\u4E0D\u61C2";
|
|
4394
|
+
}
|
|
4395
|
+
if (!d || typeof d !== "object" || d.success === false) return "\u4EFB\u52A1\u8BE6\u60C5\u770B\u4E0D\u61C2";
|
|
4396
|
+
const status = typeof d.status === "string" ? d.status.toLowerCase() : "";
|
|
4397
|
+
if (status && TERMINAL_STATUS.has(status)) return `\u4EFB\u52A1\u5DF2${status}`;
|
|
4398
|
+
const ids = pendingIds(d);
|
|
4399
|
+
if (ids.length === 0) {
|
|
4400
|
+
const mine = toolCallsOf(d).find((c) => c?.toolCallId === toolUseId);
|
|
4401
|
+
const st = mine && typeof mine.state === "string" ? mine.state : "\u672A\u627E\u5230";
|
|
4402
|
+
return `\u754C\u9762\u4E0A\u6CA1\u6709\u5728\u7B49\u7684\u4E00\u6B65\uFF08\u8FD9\u4E00\u6B65\u72B6\u6001\uFF1A${st.slice(0, 32)}\uFF09`;
|
|
4403
|
+
}
|
|
4404
|
+
if (ids.length > 1) return "\u754C\u9762\u4E0A\u540C\u65F6\u6709\u591A\u6B65\u5728\u7B49\uFF0C\u5206\u4E0D\u6E05";
|
|
4405
|
+
if (ids[0] !== toolUseId) return "\u754C\u9762\u4E0A\u5728\u7B49\u7684\u5DF2\u662F\u522B\u7684\u4E00\u6B65";
|
|
4406
|
+
return "";
|
|
4407
|
+
}
|
|
4408
|
+
function detailSettled(text, toolUseId) {
|
|
4409
|
+
let d;
|
|
4410
|
+
try {
|
|
4411
|
+
d = JSON.parse(text);
|
|
4412
|
+
} catch {
|
|
4413
|
+
return "";
|
|
4414
|
+
}
|
|
4415
|
+
if (!d || typeof d !== "object" || d.success === false) return "";
|
|
4416
|
+
const status = typeof d.status === "string" ? d.status.toLowerCase() : "";
|
|
4417
|
+
if (status && TERMINAL_STATUS.has(status)) return `\u4EFB\u52A1\u5DF2${status}`;
|
|
4418
|
+
if (!toolUseId) return "";
|
|
4419
|
+
for (const c of toolCallsOf(d)) {
|
|
4420
|
+
if (c?.toolCallId !== toolUseId) continue;
|
|
4421
|
+
const st = typeof c.state === "string" ? c.state : "";
|
|
4422
|
+
if (TOOL_DONE_STATES.has(st)) return `\u8FD9\u4E00\u6B65\u5DF2\u662F ${st}`;
|
|
4423
|
+
break;
|
|
4424
|
+
}
|
|
4425
|
+
const ids = pendingIds(d);
|
|
4426
|
+
if (ids.length > 0 && !ids.includes(toolUseId)) return "\u754C\u9762\u4E0A\u5728\u7B49\u7684\u5DF2\u662F\u522B\u7684\u4E00\u6B65";
|
|
4427
|
+
return "";
|
|
4428
|
+
}
|
|
4429
|
+
function readConfigFile(p) {
|
|
4430
|
+
try {
|
|
4431
|
+
return readFileSync13(p, "utf8");
|
|
4432
|
+
} catch {
|
|
4433
|
+
return null;
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
function sleep(ms) {
|
|
4437
|
+
return new Promise((r) => setTimeout(r, ms).unref?.());
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
// src/daemon/dumateWatch.ts
|
|
4441
|
+
import { execFileSync as execFileSync9, spawn as spawn3 } from "child_process";
|
|
4442
|
+
import { readdirSync as readdirSync4, realpathSync as realpathSync3, statSync as statSync6 } from "fs";
|
|
4443
|
+
import { request as httpRequest } from "http";
|
|
4444
|
+
import { join as join19 } from "path";
|
|
4445
|
+
|
|
4446
|
+
// hook/ocTranslate.mjs
|
|
4447
|
+
function toCcTool(tool, args) {
|
|
4448
|
+
const a = args && typeof args === "object" ? args : {};
|
|
4449
|
+
const s = (v) => typeof v === "string" ? v : "";
|
|
4450
|
+
switch (tool) {
|
|
4451
|
+
case "bash":
|
|
4452
|
+
return { tool_name: "Bash", tool_input: { command: s(a.command) } };
|
|
4453
|
+
case "read":
|
|
4454
|
+
return { tool_name: "Read", tool_input: { file_path: s(a.filePath) } };
|
|
4455
|
+
case "edit":
|
|
4456
|
+
return { tool_name: "Edit", tool_input: { file_path: s(a.filePath) } };
|
|
4457
|
+
case "write":
|
|
4458
|
+
return { tool_name: "Write", tool_input: { file_path: s(a.filePath) } };
|
|
4459
|
+
case "glob":
|
|
4460
|
+
return { tool_name: "Glob", tool_input: { pattern: s(a.pattern), path: s(a.path) } };
|
|
4461
|
+
case "grep":
|
|
4462
|
+
return { tool_name: "Grep", tool_input: { pattern: s(a.pattern), path: s(a.path) } };
|
|
4463
|
+
case "list":
|
|
4464
|
+
return { tool_name: "LS", tool_input: { path: s(a.path) } };
|
|
4465
|
+
case "webfetch":
|
|
4466
|
+
return { tool_name: "WebFetch", tool_input: { url: s(a.url) } };
|
|
4467
|
+
case "task":
|
|
4468
|
+
return { tool_name: "Task", tool_input: {} };
|
|
4469
|
+
case "todowrite":
|
|
4470
|
+
case "todoread":
|
|
4471
|
+
return { tool_name: "TodoWrite", tool_input: {} };
|
|
4472
|
+
case "patch":
|
|
4473
|
+
case "apply_patch":
|
|
4474
|
+
return { tool_name: "apply_patch", tool_input: { patch: s(a.patchText) || s(a.patch) || s(a.input) } };
|
|
4475
|
+
default:
|
|
4476
|
+
return { tool_name: String(tool || ""), tool_input: a };
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
function askToCc(props) {
|
|
4480
|
+
const md = props && props.metadata || {};
|
|
4481
|
+
if (typeof md.command === "string" && md.command) return { tool_name: "Bash", tool_input: { command: md.command } };
|
|
4482
|
+
const file = md.filepath || md.filePath;
|
|
4483
|
+
if (typeof file === "string" && file) return { tool_name: "Edit", tool_input: { file_path: file } };
|
|
4484
|
+
const patterns = Array.isArray(props && props.patterns) ? props.patterns.join(" ") : "";
|
|
4485
|
+
return { tool_name: String(props && props.permission || "permission"), tool_input: { path: patterns } };
|
|
4486
|
+
}
|
|
4487
|
+
function isPlainQuestion(props, callTool) {
|
|
4488
|
+
if (callTool !== "question") return false;
|
|
4489
|
+
const qs = props && Array.isArray(props.questions) ? props.questions : [];
|
|
4490
|
+
return !qs.some((q) => q && (q.header === "Build Agent" || typeof q.question === "string" && /^Plan at .* is complete\./.test(q.question)));
|
|
4491
|
+
}
|
|
4492
|
+
function questionToCc(props) {
|
|
4493
|
+
const qs = props && Array.isArray(props.questions) ? props.questions : null;
|
|
4494
|
+
if (!qs || qs.length === 0) return null;
|
|
4495
|
+
return {
|
|
4496
|
+
tool_name: "AskUserQuestion",
|
|
4497
|
+
tool_input: {
|
|
4498
|
+
questions: qs.map((q) => ({
|
|
4499
|
+
question: q && q.question,
|
|
4500
|
+
header: q && q.header || "",
|
|
4501
|
+
multiSelect: !!(q && q.multiple),
|
|
4502
|
+
options: q && Array.isArray(q.options) ? q.options.map((o) => ({ label: o && o.label, description: o && o.description || "" })) : [],
|
|
4503
|
+
...q && q.custom === false ? { custom: false } : {}
|
|
4504
|
+
}))
|
|
4505
|
+
}
|
|
4506
|
+
};
|
|
4507
|
+
}
|
|
4508
|
+
var OC_DEFAULT_TITLE_RE = /^(New session - |Child session - )\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
4509
|
+
function rememberTitle(titles, info) {
|
|
4510
|
+
if (!info || typeof info.id !== "string" || !info.id || info.parentID) return;
|
|
4511
|
+
const t = typeof info.title === "string" ? info.title.trim() : "";
|
|
4512
|
+
if (!t || OC_DEFAULT_TITLE_RE.test(t)) return;
|
|
4513
|
+
titles.set(info.id, t.slice(0, 100));
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4516
|
+
// src/daemon/dumateWatch.ts
|
|
4517
|
+
var ACTIVE_DIR_MS = 6 * 60 * 60 * 1e3;
|
|
4518
|
+
var MAX_POLL_DIRS = 12;
|
|
4519
|
+
var SESSION_LIST_MS = 10 * 1e3;
|
|
4520
|
+
var RESUBSCRIBE_MS = 5 * 1e3;
|
|
4521
|
+
var BUSY_WAIT_MS = 400;
|
|
4522
|
+
var QUESTION_TIMEOUT_MS = 120 * 1e3;
|
|
4523
|
+
var MISSING_GRACE_MS = 2 * 1e3;
|
|
4524
|
+
var REQUEST_TIMEOUT_MS2 = 5 * 1e3;
|
|
4525
|
+
var HOOK_KILL_MS = 10 * 1e3;
|
|
4526
|
+
var HANDLED_TTL_MS = 60 * 60 * 1e3;
|
|
4527
|
+
var SCAN_DIRS_MS = SESSION_LIST_MS;
|
|
4528
|
+
var DISCOVER_BACKOFF_MS = 10 * 1e3;
|
|
4529
|
+
var BUSY_DIR_MS = 10 * 60 * 1e3;
|
|
4530
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
4531
|
+
function questionDeadline(deadlineAt, timeoutMs, now) {
|
|
4532
|
+
if (typeof deadlineAt === "number" && Number.isFinite(deadlineAt) && deadlineAt > 0) {
|
|
4533
|
+
const ms = deadlineAt < 1e12 ? deadlineAt * 1e3 : deadlineAt;
|
|
4534
|
+
if (Math.abs(ms - now) <= DAY_MS) return ms;
|
|
4535
|
+
}
|
|
4536
|
+
if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs >= 1e3 && timeoutMs <= DAY_MS) return now + timeoutMs;
|
|
4537
|
+
return now + QUESTION_TIMEOUT_MS;
|
|
4538
|
+
}
|
|
4539
|
+
var realCache = /* @__PURE__ */ new Map();
|
|
4540
|
+
function dirKey(p) {
|
|
4541
|
+
let r = realCache.get(p);
|
|
4542
|
+
if (r === void 0) {
|
|
4543
|
+
try {
|
|
4544
|
+
r = realpathSync3(p);
|
|
4545
|
+
} catch {
|
|
4546
|
+
r = p;
|
|
4547
|
+
}
|
|
4548
|
+
if (realCache.size > 2e3) realCache.clear();
|
|
4549
|
+
realCache.set(p, r);
|
|
4550
|
+
}
|
|
4551
|
+
const t = r.length > 1 ? r.replace(/\/+$/, "") : r;
|
|
4552
|
+
return process.platform === "linux" ? t : t.toLowerCase();
|
|
4553
|
+
}
|
|
4554
|
+
function parseDumateServe(psOutput, uid) {
|
|
4555
|
+
for (const line of psOutput.split("\n")) {
|
|
4556
|
+
const m = line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/);
|
|
4557
|
+
if (!m) continue;
|
|
4558
|
+
const [, pid, u, args] = m;
|
|
4559
|
+
if (Number(u) !== uid) continue;
|
|
4560
|
+
if (!/(^|\/)dumate-opencode\s+serve(\s|$)/.test(args)) continue;
|
|
4561
|
+
const port = args.match(/--port[= ](\d{1,5})(\s|$)/);
|
|
4562
|
+
if (!port) continue;
|
|
4563
|
+
const host = args.match(/--hostname[= ](\S+)/);
|
|
4564
|
+
if (host && !["127.0.0.1", "localhost", "::1"].includes(host[1])) continue;
|
|
4565
|
+
const n = Number(port[1]);
|
|
4566
|
+
if (n <= 0 || n > 65535) continue;
|
|
4567
|
+
return { pid: Number(pid), port: n };
|
|
4568
|
+
}
|
|
4569
|
+
return null;
|
|
4570
|
+
}
|
|
4571
|
+
function parseInappKey(envOutput) {
|
|
4572
|
+
const m = envOutput.match(/(?:^|\s)DUMATE_INAPP_KEY=([^\s]+)/);
|
|
4573
|
+
return m ? m[1] : null;
|
|
4574
|
+
}
|
|
4575
|
+
var keyCache = null;
|
|
4576
|
+
function defaultDiscover() {
|
|
4577
|
+
if (process.platform === "win32" || typeof process.getuid !== "function") return null;
|
|
4578
|
+
try {
|
|
4579
|
+
const ps = execFileSync9("ps", ["-A", "-ww", "-o", "pid=,uid=,args="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
4580
|
+
const hit = parseDumateServe(ps, process.getuid());
|
|
4581
|
+
if (!hit) return null;
|
|
4582
|
+
if (keyCache && keyCache.pid === hit.pid && keyCache.port === hit.port) return keyCache;
|
|
4583
|
+
const env = execFileSync9("ps", ["eww", "-p", String(hit.pid), "-o", "command="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
4584
|
+
const key = parseInappKey(env);
|
|
4585
|
+
keyCache = key ? { ...hit, key } : null;
|
|
4586
|
+
return keyCache;
|
|
4587
|
+
} catch {
|
|
4588
|
+
return null;
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
function headersFor(t, directory) {
|
|
4592
|
+
return {
|
|
4593
|
+
"X-Dumate-Inapp-Key": t.key,
|
|
4594
|
+
// 实测 DuMate 会 decodeURIComponent 这个头:编码后中文目录名也能放进 HTTP 头
|
|
4595
|
+
...directory ? { "x-opencode-directory": encodeURIComponent(directory) } : {}
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
var defaultHttp = async (t, req2) => {
|
|
4599
|
+
const ac = new AbortController();
|
|
4600
|
+
const timer = setTimeout(() => ac.abort(), REQUEST_TIMEOUT_MS2);
|
|
4601
|
+
timer.unref?.();
|
|
4602
|
+
try {
|
|
4603
|
+
const r = await fetch(`http://127.0.0.1:${t.port}${req2.path}`, {
|
|
4604
|
+
method: req2.method,
|
|
4605
|
+
headers: { ...headersFor(t, req2.directory), ...req2.body !== void 0 ? { "content-type": "application/json" } : {} },
|
|
4606
|
+
...req2.body !== void 0 ? { body: JSON.stringify(req2.body) } : {},
|
|
4607
|
+
signal: ac.signal
|
|
4608
|
+
});
|
|
4609
|
+
const text = await r.text();
|
|
4610
|
+
let body = null;
|
|
4611
|
+
try {
|
|
4612
|
+
body = text ? JSON.parse(text) : null;
|
|
4613
|
+
} catch {
|
|
4614
|
+
body = text;
|
|
4615
|
+
}
|
|
4616
|
+
return { status: r.status, body };
|
|
4617
|
+
} catch {
|
|
4618
|
+
return { status: 0, body: null };
|
|
4619
|
+
} finally {
|
|
4620
|
+
clearTimeout(timer);
|
|
4621
|
+
}
|
|
4622
|
+
};
|
|
4623
|
+
var defaultSubscribe = (t, path, directory, onEvent, onEnd) => {
|
|
4624
|
+
let ended = false;
|
|
4625
|
+
const end = (status) => {
|
|
4626
|
+
if (ended) return;
|
|
4627
|
+
ended = true;
|
|
4628
|
+
onEnd(status);
|
|
4629
|
+
};
|
|
4630
|
+
const req2 = httpRequest(
|
|
4631
|
+
{ host: "127.0.0.1", port: t.port, path, method: "GET", headers: { accept: "text/event-stream", ...headersFor(t, directory) } },
|
|
4632
|
+
(res) => {
|
|
4633
|
+
if (res.statusCode !== 200) {
|
|
4634
|
+
res.resume();
|
|
4635
|
+
end(res.statusCode ?? 0);
|
|
4636
|
+
return;
|
|
4637
|
+
}
|
|
4638
|
+
res.setEncoding("utf8");
|
|
4639
|
+
let buf = "";
|
|
4640
|
+
res.on("data", (chunk) => {
|
|
4641
|
+
buf += chunk;
|
|
4642
|
+
let i;
|
|
4643
|
+
while ((i = buf.indexOf("\n")) >= 0) {
|
|
4644
|
+
const line = buf.slice(0, i).replace(/\r$/, "");
|
|
4645
|
+
buf = buf.slice(i + 1);
|
|
4646
|
+
if (!line.startsWith("data:")) continue;
|
|
4647
|
+
try {
|
|
4648
|
+
const v = JSON.parse(line.slice(5).trim());
|
|
4649
|
+
if (v && typeof v === "object") onEvent(v);
|
|
4650
|
+
} catch {
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
if (buf.length > 4 * 1024 * 1024) buf = "";
|
|
4654
|
+
});
|
|
4655
|
+
res.on("end", () => end(200));
|
|
4656
|
+
res.on("error", () => end(0));
|
|
4657
|
+
}
|
|
4658
|
+
);
|
|
4659
|
+
req2.on("error", () => end(0));
|
|
4660
|
+
req2.end();
|
|
4661
|
+
return () => {
|
|
4662
|
+
ended = true;
|
|
4663
|
+
req2.destroy();
|
|
4664
|
+
};
|
|
4665
|
+
};
|
|
4666
|
+
function scanSessionDirs(root, now, maxAgeMs = ACTIVE_DIR_MS) {
|
|
4667
|
+
const out = [];
|
|
4668
|
+
const list = (p) => {
|
|
4669
|
+
try {
|
|
4670
|
+
return readdirSync4(p, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join19(p, d.name));
|
|
4671
|
+
} catch {
|
|
4672
|
+
return [];
|
|
4673
|
+
}
|
|
4674
|
+
};
|
|
4675
|
+
for (const u of list(root))
|
|
4676
|
+
for (const day of list(u).sort().reverse().slice(0, 2))
|
|
4677
|
+
for (const dir of list(day)) {
|
|
4678
|
+
try {
|
|
4679
|
+
const at = statSync6(dir).mtimeMs;
|
|
4680
|
+
if (now - at <= maxAgeMs) out.push({ dir, at });
|
|
4681
|
+
} catch {
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
return out.sort((a, b) => b.at - a.at).map((x) => x.dir);
|
|
4685
|
+
}
|
|
4686
|
+
var DumateWatch = class {
|
|
4687
|
+
#d;
|
|
4688
|
+
#target = null;
|
|
4689
|
+
#stopped = false;
|
|
4690
|
+
/** 订阅:'' = /global/event;否则按目录 */
|
|
4691
|
+
#subs = /* @__PURE__ */ new Map();
|
|
4692
|
+
#subEndedAt = 0;
|
|
4693
|
+
/** /global/event 不可用(404),退到按目录订阅 */
|
|
4694
|
+
#globalUnsupported = false;
|
|
4695
|
+
#questionUnsupported = false;
|
|
4696
|
+
#permissionUnsupported = false;
|
|
4697
|
+
#reconciling = false;
|
|
4698
|
+
#sessionListAt = 0;
|
|
4699
|
+
/** 目录 → 最近一次看到它活动的时间 */
|
|
4700
|
+
/** dirKey → 原写法 + 最近一次看到它活动的时间 */
|
|
4701
|
+
#dirs = /* @__PURE__ */ new Map();
|
|
4702
|
+
/** dirKey → 最近一次忙(busy / 工具 / 提问)的时间 */
|
|
4703
|
+
#busyDirs = /* @__PURE__ */ new Map();
|
|
4704
|
+
/** 当前这条 /global/event 已经收到过带目录的会话事件:之后只轮询有卡 / 最近忙过的目录 */
|
|
4705
|
+
#sseVerified = false;
|
|
4706
|
+
#scanAt = 0;
|
|
4707
|
+
#discoverMissAt = 0;
|
|
4708
|
+
#sessionDir = /* @__PURE__ */ new Map();
|
|
4709
|
+
#titles = /* @__PURE__ */ new Map();
|
|
4710
|
+
#children = /* @__PURE__ */ new Set();
|
|
4711
|
+
#userMsgs = /* @__PURE__ */ new Map();
|
|
4712
|
+
#promptedMsgs = /* @__PURE__ */ new Set();
|
|
4713
|
+
/** callID → 工具名 / cc 同构参数(授权卡用真实参数,选择题判来源) */
|
|
4714
|
+
#calls = /* @__PURE__ */ new Map();
|
|
4715
|
+
#callReported = /* @__PURE__ */ new Map();
|
|
4716
|
+
/** 本轮已报过 UserPromptSubmit 的会话 */
|
|
4717
|
+
#turnOpen = /* @__PURE__ */ new Set();
|
|
4718
|
+
/** 报过「进行中」类事件、还没报 Stop 的会话 */
|
|
4719
|
+
#active = /* @__PURE__ */ new Set();
|
|
4720
|
+
#busyTimers = /* @__PURE__ */ new Map();
|
|
4721
|
+
#lastUserText = /* @__PURE__ */ new Map();
|
|
4722
|
+
#cards = /* @__PURE__ */ new Map();
|
|
4723
|
+
/** 推过 / 不推的请求 id → 时间:同一个只处理一次 */
|
|
4724
|
+
#handled = /* @__PURE__ */ new Map();
|
|
4725
|
+
#reportChain = Promise.resolve();
|
|
4726
|
+
constructor(deps) {
|
|
4727
|
+
this.#d = deps;
|
|
4728
|
+
}
|
|
4729
|
+
#now() {
|
|
4730
|
+
return this.#d.now ? this.#d.now() : Date.now();
|
|
4731
|
+
}
|
|
4732
|
+
get connected() {
|
|
4733
|
+
return this.#target !== null;
|
|
4734
|
+
}
|
|
4735
|
+
/** 只给测试和诊断用 */
|
|
4736
|
+
get pending() {
|
|
4737
|
+
return this.#cards.size;
|
|
4738
|
+
}
|
|
4739
|
+
/** 定时调(2~3 秒):找进程、换了就重连、补订阅、轮询待答 */
|
|
4740
|
+
tick() {
|
|
4741
|
+
if (this.#stopped) return;
|
|
4742
|
+
if (this.#d.enabled && !this.#d.enabled()) {
|
|
4743
|
+
if (this.#target) this.#teardown("\u767E\u5EA6\u642D\u5B50\u5DF2\u505C\u7528");
|
|
4744
|
+
return;
|
|
4745
|
+
}
|
|
4746
|
+
if (!this.#target && this.#discoverMissAt && this.#now() - this.#discoverMissAt < DISCOVER_BACKOFF_MS) return;
|
|
4747
|
+
let t = null;
|
|
4748
|
+
try {
|
|
4749
|
+
t = (this.#d.discover ?? defaultDiscover)();
|
|
4750
|
+
} catch {
|
|
4751
|
+
t = null;
|
|
4752
|
+
}
|
|
4753
|
+
if (!t) {
|
|
4754
|
+
if (this.#target) this.#teardown("\u767E\u5EA6\u642D\u5B50\u5DF2\u9000\u51FA");
|
|
4755
|
+
this.#discoverMissAt = this.#now();
|
|
4756
|
+
return;
|
|
4757
|
+
}
|
|
4758
|
+
this.#discoverMissAt = 0;
|
|
4759
|
+
const cur = this.#target;
|
|
4760
|
+
if (!cur || cur.pid !== t.pid || cur.port !== t.port || cur.key !== t.key) {
|
|
4761
|
+
if (cur) this.#teardown("\u767E\u5EA6\u642D\u5B50\u91CD\u542F\u8FC7");
|
|
4762
|
+
this.#target = t;
|
|
4763
|
+
this.#d.log(`\u5DF2\u8FDE\u4E0A\u767E\u5EA6\u642D\u5B50\uFF08\u7AEF\u53E3 ${t.port}\uFF09\uFF0C\u72B6\u6001\u3001\u9009\u62E9\u9898\u4E0E\u6388\u6743\u53EF\u63A8\u5230\u8BBE\u5907`);
|
|
4764
|
+
}
|
|
4765
|
+
this.#ensureSubscribed();
|
|
4766
|
+
void this.reconcile();
|
|
4767
|
+
}
|
|
4768
|
+
stop() {
|
|
4769
|
+
this.#stopped = true;
|
|
4770
|
+
this.#teardown("");
|
|
4771
|
+
}
|
|
4772
|
+
#teardown(why) {
|
|
4773
|
+
for (const unsub of this.#subs.values()) unsub();
|
|
4774
|
+
this.#subs.clear();
|
|
4775
|
+
for (const c of this.#cards.values()) c.ac.abort();
|
|
4776
|
+
this.#cards.clear();
|
|
4777
|
+
for (const t of this.#busyTimers.values()) clearTimeout(t);
|
|
4778
|
+
this.#busyTimers.clear();
|
|
4779
|
+
this.#target = null;
|
|
4780
|
+
this.#globalUnsupported = false;
|
|
4781
|
+
this.#questionUnsupported = false;
|
|
4782
|
+
this.#permissionUnsupported = false;
|
|
4783
|
+
this.#sessionListAt = 0;
|
|
4784
|
+
this.#subEndedAt = 0;
|
|
4785
|
+
this.#dirs.clear();
|
|
4786
|
+
this.#busyDirs.clear();
|
|
4787
|
+
this.#sseVerified = false;
|
|
4788
|
+
this.#scanAt = 0;
|
|
4789
|
+
this.#sessionDir.clear();
|
|
4790
|
+
this.#children.clear();
|
|
4791
|
+
this.#userMsgs.clear();
|
|
4792
|
+
this.#promptedMsgs.clear();
|
|
4793
|
+
this.#calls.clear();
|
|
4794
|
+
this.#callReported.clear();
|
|
4795
|
+
this.#turnOpen.clear();
|
|
4796
|
+
this.#active.clear();
|
|
4797
|
+
this.#lastUserText.clear();
|
|
4798
|
+
if (why) this.#d.log(`\u5DF2\u65AD\u5F00\u767E\u5EA6\u642D\u5B50\uFF08${why}\uFF09`);
|
|
4799
|
+
}
|
|
4800
|
+
// ---------- 订阅 ----------
|
|
4801
|
+
#ensureSubscribed() {
|
|
4802
|
+
const t = this.#target;
|
|
4803
|
+
if (!t) return;
|
|
4804
|
+
if (this.#subEndedAt && this.#now() - this.#subEndedAt < RESUBSCRIBE_MS) return;
|
|
4805
|
+
const sub = this.#d.subscribe ?? defaultSubscribe;
|
|
4806
|
+
if (!this.#globalUnsupported) {
|
|
4807
|
+
if (this.#subs.has("")) return;
|
|
4808
|
+
this.#subs.set(
|
|
4809
|
+
"",
|
|
4810
|
+
sub(t, "/global/event", void 0, (ev) => this.#onEnvelope(ev, void 0), (status) => this.#onSubEnd("", t, status))
|
|
4811
|
+
);
|
|
4812
|
+
return;
|
|
4813
|
+
}
|
|
4814
|
+
const active = this.#activeDirs();
|
|
4815
|
+
const keys = new Set(active.map(dirKey));
|
|
4816
|
+
for (const [k, unsub] of [...this.#subs]) {
|
|
4817
|
+
if (k === "" || keys.has(k)) continue;
|
|
4818
|
+
unsub();
|
|
4819
|
+
this.#subs.delete(k);
|
|
4820
|
+
}
|
|
4821
|
+
for (const dir of active) {
|
|
4822
|
+
const k = dirKey(dir);
|
|
4823
|
+
if (this.#subs.has(k)) continue;
|
|
4824
|
+
this.#subs.set(
|
|
4825
|
+
k,
|
|
4826
|
+
sub(t, "/event", dir, (ev) => this.#onEnvelope(ev, dir), (status) => this.#onSubEnd(k, t, status))
|
|
4827
|
+
);
|
|
4828
|
+
}
|
|
3474
4829
|
}
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
4830
|
+
#onSubEnd(key, t, status) {
|
|
4831
|
+
if (this.#target !== t) return;
|
|
4832
|
+
this.#subs.delete(key);
|
|
4833
|
+
this.#subEndedAt = this.#now();
|
|
4834
|
+
if (key === "" && status === 404) {
|
|
4835
|
+
this.#globalUnsupported = true;
|
|
4836
|
+
this.#d.log("\u767E\u5EA6\u642D\u5B50\u6CA1\u6709 /global/event\uFF0C\u6539\u4E3A\u6309\u4F1A\u8BDD\u76EE\u5F55\u8BA2\u9605");
|
|
4837
|
+
}
|
|
3478
4838
|
}
|
|
3479
|
-
/**
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
4839
|
+
/** /global/event 的信封 `{directory, payload}`;按目录的 /event 直接是事件 */
|
|
4840
|
+
#onEnvelope(raw, dir) {
|
|
4841
|
+
const inner = raw.payload && typeof raw.payload === "object" ? raw.payload : raw;
|
|
4842
|
+
const d = typeof raw.directory === "string" && raw.directory ? raw.directory : dir;
|
|
4843
|
+
if (dir === void 0 && d && inner.type !== "server.connected" && inner.type !== "server.heartbeat") this.#sseVerified = true;
|
|
4844
|
+
try {
|
|
4845
|
+
this.onEvent(inner, d);
|
|
4846
|
+
} catch (e) {
|
|
4847
|
+
this.#d.log(`\u5904\u7406\u767E\u5EA6\u642D\u5B50\u4E8B\u4EF6\u51FA\u9519\uFF1A${e?.message ?? e}`);
|
|
3483
4848
|
}
|
|
3484
4849
|
}
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
const sessionId = deps.alias ? deps.alias(cmd.sessionId) : cmd.sessionId;
|
|
3491
|
-
const state = deps.readState(sessionId);
|
|
3492
|
-
if (!state) return { ok: false, reason: "session_gone" };
|
|
3493
|
-
if (!state.cwd) return { ok: false, reason: "no_cwd" };
|
|
3494
|
-
if (deps.windowOpen?.(state)) return { ok: false, reason: "window_open" };
|
|
3495
|
-
return { ok: true, sessionId, cwd: state.cwd };
|
|
4850
|
+
#touchDir(dir, at = this.#now()) {
|
|
4851
|
+
if (!dir || !dir.startsWith("/")) return;
|
|
4852
|
+
const k = dirKey(dir);
|
|
4853
|
+
const cur = this.#dirs.get(k);
|
|
4854
|
+
if (!cur || cur.at < at) this.#dirs.set(k, { dir: cur?.dir ?? dir, at });
|
|
3496
4855
|
}
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
dirCwd = deps.dirOf ? deps.dirOf(cmd.dirRef) : "";
|
|
3500
|
-
if (!dirCwd) return { ok: false, reason: "dir_gone" };
|
|
4856
|
+
#markBusy(dir) {
|
|
4857
|
+
if (dir) this.#busyDirs.set(dirKey(dir), this.#now());
|
|
3501
4858
|
}
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
function failureMessage(reason) {
|
|
3507
|
-
switch (reason) {
|
|
3508
|
-
case "session_gone":
|
|
3509
|
-
return "\u8FD9\u4E2A\u4F1A\u8BDD\u5DF2\u7ECF\u4E0D\u5728\u4E86";
|
|
3510
|
-
case "no_cwd":
|
|
3511
|
-
return "\u8FD9\u4E2A\u4F1A\u8BDD\u9700\u8981\u66F4\u65B0 DeskBuddy \u540E\u624D\u80FD\u8FDC\u7A0B\u64CD\u4F5C";
|
|
3512
|
-
case "no_remote_cwd":
|
|
3513
|
-
return "\u8FD8\u6CA1\u8BBE\u7F6E\u8FDC\u7A0B\u4EFB\u52A1\u7684\u5DE5\u4F5C\u76EE\u5F55\uFF0C\u8BF7\u5728\u7535\u8111\u4E0A\u8DD1\u4E00\u6B21 deskbuddy setup";
|
|
3514
|
-
case "window_open":
|
|
3515
|
-
return "\u8FD9\u4E2A\u4EFB\u52A1\u6B63\u5728\u7535\u8111\u4E0A\u6253\u5F00\uFF0C\u8BF7\u5728\u7535\u8111\u4E0A\u7EE7\u7EED\uFF0C\u6216\u65B0\u5EFA\u4EFB\u52A1";
|
|
3516
|
-
case "dir_gone":
|
|
3517
|
-
return "\u7535\u8111\u4E0A\u627E\u4E0D\u5230\u8FD9\u4E2A\u76EE\u5F55\u4E86\uFF0C\u8BF7\u6362\u4E00\u4E2A\u76EE\u5F55";
|
|
4859
|
+
#activeDirs() {
|
|
4860
|
+
const now = this.#now();
|
|
4861
|
+
for (const [k, v] of this.#dirs) if (now - v.at > ACTIVE_DIR_MS) this.#dirs.delete(k);
|
|
4862
|
+
return [...this.#dirs.values()].sort((a, b) => b.at - a.at).map((v) => v.dir).slice(0, MAX_POLL_DIRS);
|
|
3518
4863
|
}
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
for (const id of initial) this.#push(id);
|
|
4864
|
+
/**
|
|
4865
|
+
* 这一轮要轮询的目录(评审 d):事件流还没证实收得到会话事件时全部活跃目录;
|
|
4866
|
+
* 证实之后只轮询有卡在等的、最近忙过的(提问 / 授权只会出现在忙的会话里,事件丢了靠这个兜)
|
|
4867
|
+
*/
|
|
4868
|
+
#pollDirs() {
|
|
4869
|
+
const all = this.#activeDirs();
|
|
4870
|
+
if (!this.#sseVerified || !this.#subs.has("")) return all;
|
|
4871
|
+
const now = this.#now();
|
|
4872
|
+
const cardKeys = new Set([...this.#cards.values()].filter((c) => c.dir).map((c) => dirKey(c.dir)));
|
|
4873
|
+
return all.filter((d) => {
|
|
4874
|
+
const k = dirKey(d);
|
|
4875
|
+
return cardKeys.has(k) || now - (this.#busyDirs.get(k) ?? 0) <= BUSY_DIR_MS;
|
|
4876
|
+
});
|
|
3533
4877
|
}
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
4878
|
+
// ---------- 事件 → 状态 ----------
|
|
4879
|
+
/** 一条 opencode 事件(`{type, properties}`)。dir = 事件所属会话目录 */
|
|
4880
|
+
onEvent(ev, dir) {
|
|
4881
|
+
const type = typeof ev.type === "string" ? ev.type : "";
|
|
4882
|
+
const p = ev.properties && typeof ev.properties === "object" ? ev.properties : {};
|
|
4883
|
+
this.#touchDir(dir);
|
|
4884
|
+
switch (type) {
|
|
4885
|
+
case "session.created":
|
|
4886
|
+
case "session.updated": {
|
|
4887
|
+
const info = p.info ?? {};
|
|
4888
|
+
this.#noteSession(info);
|
|
4889
|
+
return;
|
|
4890
|
+
}
|
|
4891
|
+
case "session.deleted": {
|
|
4892
|
+
const info = p.info ?? {};
|
|
4893
|
+
const sid = typeof info.id === "string" ? info.id : typeof p.sessionID === "string" ? p.sessionID : "";
|
|
4894
|
+
if (sid && !this.#children.has(sid)) this.#report("SessionEnd", sid, dir);
|
|
4895
|
+
return;
|
|
4896
|
+
}
|
|
4897
|
+
case "session.status": {
|
|
4898
|
+
const sid = typeof p.sessionID === "string" ? p.sessionID : "";
|
|
4899
|
+
const st = p.status?.type;
|
|
4900
|
+
if (!sid) return;
|
|
4901
|
+
if (st === "busy") {
|
|
4902
|
+
this.#markBusy(this.#dirOf(sid, dir));
|
|
4903
|
+
this.#onBusy(sid, dir);
|
|
4904
|
+
} else if (st === "idle") this.#onIdle(sid, dir);
|
|
4905
|
+
return;
|
|
4906
|
+
}
|
|
4907
|
+
case "session.idle":
|
|
4908
|
+
if (typeof p.sessionID === "string") this.#onIdle(p.sessionID, dir);
|
|
4909
|
+
return;
|
|
4910
|
+
case "message.updated": {
|
|
4911
|
+
const info = p.info ?? {};
|
|
4912
|
+
if (info.role === "user" && typeof info.id === "string" && typeof info.sessionID === "string") {
|
|
4913
|
+
this.#userMsgs.set(info.id, info.sessionID);
|
|
4914
|
+
while (this.#userMsgs.size > 500) this.#userMsgs.delete(this.#userMsgs.keys().next().value);
|
|
4915
|
+
}
|
|
4916
|
+
return;
|
|
4917
|
+
}
|
|
4918
|
+
case "message.part.updated":
|
|
4919
|
+
this.#onPart(p.part ?? {}, dir);
|
|
4920
|
+
return;
|
|
4921
|
+
case "question.asked":
|
|
4922
|
+
this.#onQuestion(p, dir);
|
|
4923
|
+
return;
|
|
4924
|
+
case "question.replied":
|
|
4925
|
+
case "question.rejected":
|
|
4926
|
+
this.#settle(typeof p.requestID === "string" ? p.requestID : "", "\u7535\u8111\u4E0A\u5DF2\u7B54");
|
|
4927
|
+
return;
|
|
4928
|
+
case "permission.asked":
|
|
4929
|
+
case "permission.updated":
|
|
4930
|
+
this.#onPermission(p, dir);
|
|
4931
|
+
return;
|
|
4932
|
+
case "permission.replied":
|
|
4933
|
+
this.#settle(typeof p.requestID === "string" ? p.requestID : typeof p.permissionID === "string" ? p.permissionID : "", "\u7535\u8111\u4E0A\u5DF2\u5904\u7406");
|
|
4934
|
+
return;
|
|
4935
|
+
default:
|
|
3541
4936
|
}
|
|
3542
4937
|
}
|
|
3543
|
-
|
|
3544
|
-
|
|
4938
|
+
#noteSession(info) {
|
|
4939
|
+
const id = typeof info.id === "string" ? info.id : "";
|
|
4940
|
+
if (!id) return;
|
|
4941
|
+
if (typeof info.directory === "string" && info.directory) {
|
|
4942
|
+
this.#sessionDir.set(id, info.directory);
|
|
4943
|
+
}
|
|
4944
|
+
if (info.parentID || info.is_subagent === true) this.#children.add(id);
|
|
4945
|
+
rememberTitle(this.#titles, info);
|
|
3545
4946
|
}
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
if (this.#set.has(id)) return false;
|
|
3549
|
-
this.#push(id);
|
|
3550
|
-
return true;
|
|
4947
|
+
#dirOf(sid, dir) {
|
|
4948
|
+
return this.#sessionDir.get(sid) ?? dir ?? "";
|
|
3551
4949
|
}
|
|
3552
|
-
|
|
3553
|
-
|
|
4950
|
+
#onBusy(sid, dir) {
|
|
4951
|
+
if (this.#turnOpen.has(sid) || this.#busyTimers.has(sid)) return;
|
|
4952
|
+
const timer = setTimeout(() => {
|
|
4953
|
+
this.#busyTimers.delete(sid);
|
|
4954
|
+
this.#openTurn(sid, this.#lastUserText.get(sid), dir);
|
|
4955
|
+
}, BUSY_WAIT_MS);
|
|
4956
|
+
timer.unref?.();
|
|
4957
|
+
this.#busyTimers.set(sid, timer);
|
|
4958
|
+
}
|
|
4959
|
+
#openTurn(sid, prompt, dir) {
|
|
4960
|
+
if (this.#turnOpen.has(sid)) return;
|
|
4961
|
+
const t = this.#busyTimers.get(sid);
|
|
4962
|
+
if (t) {
|
|
4963
|
+
clearTimeout(t);
|
|
4964
|
+
this.#busyTimers.delete(sid);
|
|
4965
|
+
}
|
|
4966
|
+
this.#turnOpen.add(sid);
|
|
4967
|
+
this.#report("UserPromptSubmit", sid, dir, prompt ? { prompt } : {});
|
|
4968
|
+
}
|
|
4969
|
+
#onIdle(sid, dir) {
|
|
4970
|
+
const t = this.#busyTimers.get(sid);
|
|
4971
|
+
if (t) {
|
|
4972
|
+
this.#openTurn(sid, this.#lastUserText.get(sid), dir);
|
|
4973
|
+
}
|
|
4974
|
+
const was = this.#turnOpen.delete(sid) || this.#active.has(sid);
|
|
4975
|
+
this.#active.delete(sid);
|
|
4976
|
+
this.#lastUserText.delete(sid);
|
|
4977
|
+
if (was) this.#report("Stop", sid, dir);
|
|
4978
|
+
}
|
|
4979
|
+
#onPart(part, dir) {
|
|
4980
|
+
const sid = typeof part.sessionID === "string" ? part.sessionID : "";
|
|
4981
|
+
if (!sid) return;
|
|
4982
|
+
if (part.type === "text") {
|
|
4983
|
+
const mid = typeof part.messageID === "string" ? part.messageID : "";
|
|
4984
|
+
if (!mid || this.#userMsgs.get(mid) !== sid || part.synthetic === true) return;
|
|
4985
|
+
const text = typeof part.text === "string" ? part.text : "";
|
|
4986
|
+
if (!text.trim() || this.#promptedMsgs.has(mid)) return;
|
|
4987
|
+
this.#promptedMsgs.add(mid);
|
|
4988
|
+
while (this.#promptedMsgs.size > 500) this.#promptedMsgs.delete(this.#promptedMsgs.values().next().value);
|
|
4989
|
+
this.#lastUserText.set(sid, text);
|
|
4990
|
+
if (!this.#turnOpen.has(sid)) this.#openTurn(sid, text, dir);
|
|
4991
|
+
return;
|
|
4992
|
+
}
|
|
4993
|
+
if (part.type !== "tool") return;
|
|
4994
|
+
this.#markBusy(this.#dirOf(sid, dir));
|
|
4995
|
+
const callID = typeof part.callID === "string" ? part.callID : "";
|
|
4996
|
+
const tool = typeof part.tool === "string" ? part.tool : "";
|
|
4997
|
+
if (!callID || !tool) return;
|
|
4998
|
+
const state = part.state ?? {};
|
|
4999
|
+
const cc = toCcTool(tool, state.input);
|
|
5000
|
+
this.#calls.set(callID, { tool, cc });
|
|
5001
|
+
while (this.#calls.size > 500) this.#calls.delete(this.#calls.keys().next().value);
|
|
5002
|
+
const status = state.status;
|
|
5003
|
+
const done = this.#callReported.get(callID);
|
|
5004
|
+
if (status === "running" && !done) {
|
|
5005
|
+
this.#callReported.set(callID, "pre");
|
|
5006
|
+
this.#active.add(sid);
|
|
5007
|
+
this.#report("PreToolUse", sid, dir, cc);
|
|
5008
|
+
} else if ((status === "completed" || status === "error") && done !== "post") {
|
|
5009
|
+
this.#callReported.set(callID, "post");
|
|
5010
|
+
this.#active.add(sid);
|
|
5011
|
+
this.#report("PostToolUse", sid, dir, cc);
|
|
5012
|
+
}
|
|
5013
|
+
while (this.#callReported.size > 500) this.#callReported.delete(this.#callReported.keys().next().value);
|
|
5014
|
+
}
|
|
5015
|
+
#report(event, sid, dir, extra = {}) {
|
|
5016
|
+
if (this.#children.has(sid)) return;
|
|
5017
|
+
const t = this.#target;
|
|
5018
|
+
if (!t) return;
|
|
5019
|
+
const input = { hook_event_name: event, session_id: sid, cwd: this.#dirOf(sid, dir), ...extra };
|
|
5020
|
+
const title = this.#titles.get(sid);
|
|
5021
|
+
if (title) input.oc_session_title = title;
|
|
5022
|
+
const run2 = this.#d.report ?? ((i, pid) => runHook(this.#d.nodePath ?? process.execPath, i, pid));
|
|
5023
|
+
this.#reportChain = this.#reportChain.then(() => run2(input, t.pid)).catch(() => {
|
|
5024
|
+
});
|
|
3554
5025
|
}
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
5026
|
+
// ---------- 选择题 / 授权 ----------
|
|
5027
|
+
#prune() {
|
|
5028
|
+
const now = this.#now();
|
|
5029
|
+
for (const [id, at] of this.#handled) if (now - at > HANDLED_TTL_MS) this.#handled.delete(id);
|
|
5030
|
+
}
|
|
5031
|
+
#onQuestion(p, dir) {
|
|
5032
|
+
const id = typeof p.id === "string" ? p.id : "";
|
|
5033
|
+
const sid = typeof p.sessionID === "string" ? p.sessionID : "";
|
|
5034
|
+
if (!id || !sid || this.#cards.has(id) || this.#handled.has(id) || !this.#target) return;
|
|
5035
|
+
this.#prune();
|
|
5036
|
+
this.#handled.set(id, this.#now());
|
|
5037
|
+
const callID = (p.tool ?? {}).callID;
|
|
5038
|
+
const callTool = typeof callID === "string" ? this.#calls.get(callID)?.tool ?? "question" : "question";
|
|
5039
|
+
if (!isPlainQuestion(p, callTool)) return;
|
|
5040
|
+
const cc = questionToCc(p);
|
|
5041
|
+
if (!cc) return;
|
|
5042
|
+
const d = this.#dirOf(sid, dir);
|
|
5043
|
+
const deadline = questionDeadline(p.deadlineAt, p.timeoutMs, this.#now());
|
|
5044
|
+
this.#push({ id, kind: "question", sessionID: sid, dir: d, deadlineAt: deadline }, cc);
|
|
5045
|
+
}
|
|
5046
|
+
#onPermission(p, dir) {
|
|
5047
|
+
const id = typeof p.id === "string" ? p.id : "";
|
|
5048
|
+
const sid = typeof p.sessionID === "string" ? p.sessionID : "";
|
|
5049
|
+
if (!id || !sid || this.#cards.has(id) || this.#handled.has(id) || !this.#target) return;
|
|
5050
|
+
this.#prune();
|
|
5051
|
+
this.#handled.set(id, this.#now());
|
|
5052
|
+
const callID = (p.tool ?? {}).callID;
|
|
5053
|
+
const call2 = typeof callID === "string" ? this.#calls.get(callID) : void 0;
|
|
5054
|
+
const cc = call2 ? call2.cc : askToCc(p);
|
|
5055
|
+
this.#report("PermissionRequest", sid, dir, cc);
|
|
5056
|
+
this.#push({ id, kind: "permission", sessionID: sid, dir: this.#dirOf(sid, dir), deadlineAt: 0 }, cc);
|
|
5057
|
+
}
|
|
5058
|
+
#push(c, cc) {
|
|
5059
|
+
const t = this.#target;
|
|
5060
|
+
if (!t) return;
|
|
5061
|
+
const card = { ...c, ac: new AbortController(), createdAt: this.#now() };
|
|
5062
|
+
this.#cards.set(card.id, card);
|
|
5063
|
+
const req2 = {
|
|
5064
|
+
window: true,
|
|
5065
|
+
tool: "dumate",
|
|
5066
|
+
hook: { hook_event_name: "PermissionRequest", session_id: card.sessionID, cwd: card.dir, tool_name: cc.tool_name, tool_input: cc.tool_input }
|
|
5067
|
+
};
|
|
5068
|
+
void this.#d.gate(req2, card.ac.signal).then(async (reply) => {
|
|
5069
|
+
if (this.#cards.get(card.id) !== card || card.ac.signal.aborted || this.#target !== t) return;
|
|
5070
|
+
this.#cards.delete(card.id);
|
|
5071
|
+
await this.#answer(t, card, reply);
|
|
5072
|
+
}).catch(() => {
|
|
5073
|
+
if (this.#cards.get(card.id) === card) this.#cards.delete(card.id);
|
|
5074
|
+
});
|
|
5075
|
+
}
|
|
5076
|
+
async #answer(t, card, reply) {
|
|
5077
|
+
const http = this.#d.http ?? defaultHttp;
|
|
5078
|
+
const short = card.id.slice(0, 12);
|
|
5079
|
+
let r;
|
|
5080
|
+
if (card.kind === "question") {
|
|
5081
|
+
if (reply.decision === "allow" && Array.isArray(reply.answerLists)) {
|
|
5082
|
+
r = await http(t, { method: "POST", path: `/question/${encodeURIComponent(card.id)}/reply`, directory: card.dir, body: { answers: reply.answerLists } });
|
|
5083
|
+
} else if (reply.decision === "deny") {
|
|
5084
|
+
r = await http(t, { method: "POST", path: `/question/${encodeURIComponent(card.id)}/reject`, directory: card.dir });
|
|
5085
|
+
} else return;
|
|
5086
|
+
} else {
|
|
5087
|
+
if (reply.decision !== "allow" && reply.decision !== "deny") return;
|
|
5088
|
+
const answer = reply.decision === "allow" ? "once" : "reject";
|
|
5089
|
+
r = await http(t, { method: "POST", path: `/permission/${encodeURIComponent(card.id)}/reply`, directory: card.dir, body: { reply: answer } });
|
|
5090
|
+
if (r.status === 404) {
|
|
5091
|
+
r = await http(t, {
|
|
5092
|
+
method: "POST",
|
|
5093
|
+
path: `/session/${encodeURIComponent(card.sessionID)}/permissions/${encodeURIComponent(card.id)}`,
|
|
5094
|
+
directory: card.dir,
|
|
5095
|
+
body: { response: answer }
|
|
5096
|
+
});
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
if (r.status >= 200 && r.status < 300) {
|
|
5100
|
+
this.#d.log(`\u767E\u5EA6\u642D\u5B50\uFF1A${card.kind === "question" ? "\u9009\u62E9\u9898" : "\u6388\u6743"} ${short} \u5DF2\u6309\u8BBE\u5907\u7B54\u590D\u56DE\u586B`);
|
|
5101
|
+
} else if (r.status === 404) {
|
|
5102
|
+
this.#d.log(`\u767E\u5EA6\u642D\u5B50\uFF1A${short} \u5DF2\u4E0D\u5728\u7B49\uFF08\u7535\u8111\u4E0A\u5DF2\u5904\u7406\u6216\u5DF2\u8D85\u65F6\uFF09\uFF0C\u8BBE\u5907\u7B54\u590D\u4E0D\u518D\u56DE\u586B`);
|
|
5103
|
+
} else {
|
|
5104
|
+
this.#d.log(`\u767E\u5EA6\u642D\u5B50\uFF1A\u56DE\u586B ${short} \u5931\u8D25\uFF08HTTP ${r.status || "\u8FDE\u4E0D\u4E0A"}\uFF09\uFF0C\u754C\u9762\u7559\u7ED9\u4EBA\u7B54`);
|
|
3561
5105
|
}
|
|
3562
5106
|
}
|
|
3563
|
-
|
|
5107
|
+
#settle(id, why) {
|
|
5108
|
+
if (!id) return;
|
|
5109
|
+
this.#handled.set(id, this.#now());
|
|
5110
|
+
const c = this.#cards.get(id);
|
|
5111
|
+
if (!c) return;
|
|
5112
|
+
this.#cards.delete(id);
|
|
5113
|
+
this.#d.log(`\u767E\u5EA6\u642D\u5B50\uFF1A${c.kind === "question" ? "\u9009\u62E9\u9898" : "\u6388\u6743"} ${id.slice(0, 12)} ${why}\uFF0C\u64A4\u5361`);
|
|
5114
|
+
c.ac.abort();
|
|
5115
|
+
}
|
|
5116
|
+
/**
|
|
5117
|
+
* 每个 tick:补会话目录、按目录轮询 /question 与 /permission(事件没订上或丢了时兜底推卡),
|
|
5118
|
+
* 轮询里不见了的卡、过了截止时间的选择题撤掉
|
|
5119
|
+
*/
|
|
5120
|
+
async reconcile() {
|
|
5121
|
+
const t = this.#target;
|
|
5122
|
+
if (!t || this.#reconciling) return;
|
|
5123
|
+
this.#reconciling = true;
|
|
3564
5124
|
try {
|
|
3565
|
-
|
|
3566
|
-
const
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
5125
|
+
const http = this.#d.http ?? defaultHttp;
|
|
5126
|
+
const now = this.#now();
|
|
5127
|
+
if (now - this.#scanAt >= SCAN_DIRS_MS) {
|
|
5128
|
+
this.#scanAt = now;
|
|
5129
|
+
for (const d of (this.#d.sessionDirs ?? (() => scanSessionDirs(join19(qianfanHome(), "workspace", "sessions"), now)))()) {
|
|
5130
|
+
if (!this.#dirs.has(dirKey(d))) this.#touchDir(d, now);
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
if (now - this.#sessionListAt >= SESSION_LIST_MS) {
|
|
5134
|
+
this.#sessionListAt = now;
|
|
5135
|
+
const r = await http(t, { method: "GET", path: "/session" });
|
|
5136
|
+
if (this.#target !== t) return;
|
|
5137
|
+
if (r.status === 200 && Array.isArray(r.body)) {
|
|
5138
|
+
for (const s of r.body) {
|
|
5139
|
+
if (!s || typeof s !== "object") continue;
|
|
5140
|
+
this.#noteSession(s);
|
|
5141
|
+
const updated = Number((s.time ?? {}).updated) || 0;
|
|
5142
|
+
if (typeof s.directory === "string" && s.directory && now - updated <= ACTIVE_DIR_MS) this.#touchDir(s.directory, updated);
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5147
|
+
const polled = /* @__PURE__ */ new Set();
|
|
5148
|
+
for (const dir of this.#pollDirs()) {
|
|
5149
|
+
const k = dirKey(dir);
|
|
5150
|
+
if (!this.#questionUnsupported) {
|
|
5151
|
+
const q = await http(t, { method: "GET", path: "/question", directory: dir });
|
|
5152
|
+
if (this.#target !== t) return;
|
|
5153
|
+
if (q.status === 404) {
|
|
5154
|
+
this.#questionUnsupported = true;
|
|
5155
|
+
this.#d.log("\u767E\u5EA6\u642D\u5B50\u6CA1\u6709 /question\uFF0C\u9009\u62E9\u9898\u4E0D\u63A8\u8BBE\u5907\uFF0C\u53EA\u4E0A\u62A5\u72B6\u6001");
|
|
5156
|
+
} else if (q.status === 200 && Array.isArray(q.body)) {
|
|
5157
|
+
polled.add(`question:${k}`);
|
|
5158
|
+
for (const item of q.body) {
|
|
5159
|
+
if (!item || typeof item.id !== "string") continue;
|
|
5160
|
+
seen.add(item.id);
|
|
5161
|
+
this.#onQuestion(item, dir);
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
if (!this.#permissionUnsupported) {
|
|
5166
|
+
const pr = await http(t, { method: "GET", path: "/permission", directory: dir });
|
|
5167
|
+
if (this.#target !== t) return;
|
|
5168
|
+
if (pr.status === 404) {
|
|
5169
|
+
this.#permissionUnsupported = true;
|
|
5170
|
+
this.#d.log("\u767E\u5EA6\u642D\u5B50\u6CA1\u6709 /permission\uFF0C\u6388\u6743\u4E0D\u63A8\u8BBE\u5907\uFF0C\u53EA\u4E0A\u62A5\u72B6\u6001");
|
|
5171
|
+
} else if (pr.status === 200 && Array.isArray(pr.body)) {
|
|
5172
|
+
polled.add(`permission:${k}`);
|
|
5173
|
+
for (const item of pr.body) {
|
|
5174
|
+
if (!item || typeof item.id !== "string") continue;
|
|
5175
|
+
seen.add(item.id);
|
|
5176
|
+
this.#onPermission(item, dir);
|
|
5177
|
+
}
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
}
|
|
5181
|
+
const later = this.#now();
|
|
5182
|
+
for (const c of [...this.#cards.values()]) {
|
|
5183
|
+
if (c.kind === "question" && c.deadlineAt > 0 && later > c.deadlineAt) {
|
|
5184
|
+
this.#settle(c.id, "\u5DF2\u8D85\u65F6");
|
|
5185
|
+
continue;
|
|
5186
|
+
}
|
|
5187
|
+
if (c.dir && polled.has(`${c.kind}:${dirKey(c.dir)}`) && !seen.has(c.id) && later - c.createdAt >= MISSING_GRACE_MS) {
|
|
5188
|
+
this.#settle(c.id, "\u5DF2\u4E0D\u5728\u7B49\uFF08\u7535\u8111\u4E0A\u5DF2\u5904\u7406\uFF09");
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
} finally {
|
|
5192
|
+
this.#reconciling = false;
|
|
3570
5193
|
}
|
|
3571
5194
|
}
|
|
3572
5195
|
};
|
|
5196
|
+
function runHook(node, input, agentPid) {
|
|
5197
|
+
return new Promise((resolve7) => {
|
|
5198
|
+
let child;
|
|
5199
|
+
try {
|
|
5200
|
+
child = spawn3(node, [installedHookPath(), "dumate"], {
|
|
5201
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
5202
|
+
env: { ...process.env, DESKBUDDY_AGENT_PID: String(agentPid) }
|
|
5203
|
+
});
|
|
5204
|
+
} catch {
|
|
5205
|
+
resolve7();
|
|
5206
|
+
return;
|
|
5207
|
+
}
|
|
5208
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), HOOK_KILL_MS);
|
|
5209
|
+
timer.unref?.();
|
|
5210
|
+
child.on("error", () => {
|
|
5211
|
+
clearTimeout(timer);
|
|
5212
|
+
resolve7();
|
|
5213
|
+
});
|
|
5214
|
+
child.on("close", () => {
|
|
5215
|
+
clearTimeout(timer);
|
|
5216
|
+
resolve7();
|
|
5217
|
+
});
|
|
5218
|
+
child.stdin.on("error", () => {
|
|
5219
|
+
});
|
|
5220
|
+
child.stdin.end(JSON.stringify(input));
|
|
5221
|
+
});
|
|
5222
|
+
}
|
|
3573
5223
|
|
|
3574
5224
|
// src/daemon/codexWatch.ts
|
|
3575
|
-
import { execFileSync as
|
|
5225
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3576
5226
|
import WebSocket from "ws";
|
|
3577
5227
|
var TURN_LIMIT_MS = 10 * 60 * 1e3;
|
|
3578
5228
|
var QUEUE_MAX_MS = 10 * 60 * 1e3;
|
|
3579
5229
|
var IDLE_GRACE_MS = 5 * 1e3;
|
|
3580
5230
|
var RESUME_RETRY_MS = 1e3;
|
|
3581
5231
|
var RESUME_MAX_TRIES = 120;
|
|
3582
|
-
var
|
|
5232
|
+
var REQUEST_TIMEOUT_MS3 = 30 * 1e3;
|
|
3583
5233
|
function finalReply(items) {
|
|
3584
5234
|
let last = "";
|
|
3585
5235
|
let final = "";
|
|
@@ -3617,7 +5267,7 @@ function discoverAppServers(configured) {
|
|
|
3617
5267
|
return parseAppServerUrls((winProcs() ?? []).map((p) => p.cmdline).join("\n"));
|
|
3618
5268
|
}
|
|
3619
5269
|
try {
|
|
3620
|
-
const out =
|
|
5270
|
+
const out = execFileSync10("ps", ["-A", "-ww", "-o", "args="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
3621
5271
|
return parseAppServerUrls(out);
|
|
3622
5272
|
} catch {
|
|
3623
5273
|
return [];
|
|
@@ -3647,6 +5297,7 @@ var CodexWatch = class {
|
|
|
3647
5297
|
/** fileChange 条目 → 改动的文件(授权请求本身不带文件,从 item/started 记) */
|
|
3648
5298
|
#changes = /* @__PURE__ */ new Map();
|
|
3649
5299
|
/** 服务端请求 id → 放掉等待的开关、所属会话 */
|
|
5300
|
+
/** 等设备答复的服务端请求。qids:选择题各题的 Codex 题目 id(按顺序),只有选择题有 —— 跟着这条记录走,不单独按请求 id 存(重连后 id 会从头编号) */
|
|
3650
5301
|
#pending = /* @__PURE__ */ new Map();
|
|
3651
5302
|
#stopped = false;
|
|
3652
5303
|
/** 已空闲的会话(有卡片在等时暂缓退订,卡片了结后补退) */
|
|
@@ -3760,13 +5411,13 @@ var CodexWatch = class {
|
|
|
3760
5411
|
}
|
|
3761
5412
|
#request(method, params) {
|
|
3762
5413
|
const id = this.#nextId++;
|
|
3763
|
-
return new Promise((
|
|
5414
|
+
return new Promise((resolve7) => {
|
|
3764
5415
|
const timer = setTimeout(() => {
|
|
3765
5416
|
this.#calls.delete(id);
|
|
3766
|
-
|
|
3767
|
-
},
|
|
5417
|
+
resolve7({ error: { message: "timeout" }, local: true });
|
|
5418
|
+
}, REQUEST_TIMEOUT_MS3);
|
|
3768
5419
|
timer.unref?.();
|
|
3769
|
-
this.#calls.set(id, { resolve:
|
|
5420
|
+
this.#calls.set(id, { resolve: resolve7, timer });
|
|
3770
5421
|
this.#send({ id, method, params });
|
|
3771
5422
|
});
|
|
3772
5423
|
}
|
|
@@ -3989,7 +5640,7 @@ var CodexWatch = class {
|
|
|
3989
5640
|
return { ...out, ...merged ? { merged } : {} };
|
|
3990
5641
|
}
|
|
3991
5642
|
#trackTurn(threadId, turnId) {
|
|
3992
|
-
return new Promise((
|
|
5643
|
+
return new Promise((resolve7) => {
|
|
3993
5644
|
const limit = this.#d.turnLimitMs ?? TURN_LIMIT_MS;
|
|
3994
5645
|
const step = this.#d.turnTickMs ?? 1e3;
|
|
3995
5646
|
const t = {
|
|
@@ -3999,7 +5650,7 @@ var CodexWatch = class {
|
|
|
3999
5650
|
used: 0,
|
|
4000
5651
|
timer: null,
|
|
4001
5652
|
grace: null,
|
|
4002
|
-
done: (end, text, error) =>
|
|
5653
|
+
done: (end, text, error) => resolve7({ sent: true, end, turnId, text, ...error ? { error } : {} })
|
|
4003
5654
|
};
|
|
4004
5655
|
this.#turns.set(turnId, t);
|
|
4005
5656
|
const early = this.#early.get(turnId);
|
|
@@ -4046,7 +5697,7 @@ var CodexWatch = class {
|
|
|
4046
5697
|
t.done(end, text || (end === "completed" ? "" : finalReply(t.items)), error);
|
|
4047
5698
|
}
|
|
4048
5699
|
#waitThread(threadId, ms) {
|
|
4049
|
-
return new Promise((
|
|
5700
|
+
return new Promise((resolve7) => {
|
|
4050
5701
|
let set = this.#wake.get(threadId);
|
|
4051
5702
|
if (!set) this.#wake.set(threadId, set = /* @__PURE__ */ new Set());
|
|
4052
5703
|
const s = set;
|
|
@@ -4054,7 +5705,7 @@ var CodexWatch = class {
|
|
|
4054
5705
|
clearTimeout(timer);
|
|
4055
5706
|
s.delete(fire);
|
|
4056
5707
|
if (s.size === 0 && this.#wake.get(threadId) === s) this.#wake.delete(threadId);
|
|
4057
|
-
|
|
5708
|
+
resolve7();
|
|
4058
5709
|
};
|
|
4059
5710
|
const timer = setTimeout(fire, ms);
|
|
4060
5711
|
timer.unref?.();
|
|
@@ -4188,6 +5839,7 @@ var CodexWatch = class {
|
|
|
4188
5839
|
const threadId = typeof p.threadId === "string" ? p.threadId : "";
|
|
4189
5840
|
if (!threadId) return;
|
|
4190
5841
|
let hook;
|
|
5842
|
+
let qids;
|
|
4191
5843
|
if (method === "item/commandExecution/requestApproval") {
|
|
4192
5844
|
const command = typeof p.command === "string" ? unwrapShell(p.command) : "";
|
|
4193
5845
|
if (!command) return;
|
|
@@ -4198,12 +5850,17 @@ var CodexWatch = class {
|
|
|
4198
5850
|
const shown = paths.join(", ");
|
|
4199
5851
|
if (!shown) return;
|
|
4200
5852
|
hook = { tool_name: "Edit", tool_input: { file_path: shown }, cwd: this.#cwd.get(threadId) ?? "" };
|
|
5853
|
+
} else if (method === "item/tool/requestUserInput") {
|
|
5854
|
+
const q = codexQuestionInput(p);
|
|
5855
|
+
if (!q) return;
|
|
5856
|
+
hook = { tool_name: "AskUserQuestion", tool_input: q.toolInput, cwd: this.#cwd.get(threadId) ?? "" };
|
|
5857
|
+
qids = q.ids;
|
|
4201
5858
|
} else {
|
|
4202
5859
|
return;
|
|
4203
5860
|
}
|
|
4204
5861
|
const key = String(id);
|
|
4205
5862
|
const ac = new AbortController();
|
|
4206
|
-
const entry = { ac, threadId };
|
|
5863
|
+
const entry = { ac, threadId, ...qids ? { qids } : {} };
|
|
4207
5864
|
this.#pending.set(key, entry);
|
|
4208
5865
|
const req2 = {
|
|
4209
5866
|
window: true,
|
|
@@ -4216,7 +5873,14 @@ var CodexWatch = class {
|
|
|
4216
5873
|
void this.#d.gate(req2, ac.signal).then((reply) => {
|
|
4217
5874
|
if (this.#pending.get(key) !== entry) return;
|
|
4218
5875
|
this.#pending.delete(key);
|
|
4219
|
-
if (
|
|
5876
|
+
if (qids) {
|
|
5877
|
+
const lists = reply.decision === "allow" ? reply.answerLists : void 0;
|
|
5878
|
+
if (Array.isArray(lists) && lists.length === qids.length) {
|
|
5879
|
+
const answers = {};
|
|
5880
|
+
qids.forEach((qid, i) => answers[qid] = { answers: lists[i] });
|
|
5881
|
+
this.#send({ id, result: { answers } });
|
|
5882
|
+
}
|
|
5883
|
+
} else if (reply.decision === "allow") this.#send({ id, result: { decision: "accept" } });
|
|
4220
5884
|
else if (reply.decision === "deny") this.#send({ id, result: { decision: "decline" } });
|
|
4221
5885
|
settled();
|
|
4222
5886
|
}).catch(() => {
|
|
@@ -4225,12 +5889,34 @@ var CodexWatch = class {
|
|
|
4225
5889
|
});
|
|
4226
5890
|
}
|
|
4227
5891
|
};
|
|
5892
|
+
function codexQuestionInput(p) {
|
|
5893
|
+
const qs = p.questions;
|
|
5894
|
+
if (!Array.isArray(qs) || qs.length === 0) return null;
|
|
5895
|
+
const ids = [];
|
|
5896
|
+
const questions = [];
|
|
5897
|
+
for (const q of qs) {
|
|
5898
|
+
if (!q || typeof q !== "object" || typeof q.id !== "string" || typeof q.question !== "string") return null;
|
|
5899
|
+
if (ids.includes(q.id)) return null;
|
|
5900
|
+
if (q.isSecret === true) return null;
|
|
5901
|
+
if (!Array.isArray(q.options) || q.options.length < 2) return null;
|
|
5902
|
+
ids.push(q.id);
|
|
5903
|
+
questions.push({
|
|
5904
|
+
question: q.question,
|
|
5905
|
+
header: typeof q.header === "string" ? q.header : "",
|
|
5906
|
+
multiSelect: false,
|
|
5907
|
+
options: q.options.map((o) => ({ label: o?.label, description: typeof o?.description === "string" ? o.description : "" })),
|
|
5908
|
+
// isOther=true 才允许自己填写(Codex 的 TUI 这时多一个「Other」)
|
|
5909
|
+
...q.isOther === true ? {} : { custom: false }
|
|
5910
|
+
});
|
|
5911
|
+
}
|
|
5912
|
+
return { toolInput: { questions }, ids };
|
|
5913
|
+
}
|
|
4228
5914
|
|
|
4229
5915
|
// src/core/codexRemote.ts
|
|
4230
5916
|
import { mkdirSync as mkdirSync14, renameSync as renameSync10, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
4231
|
-
import { join as
|
|
5917
|
+
import { join as join20 } from "path";
|
|
4232
5918
|
function codexRemotePath() {
|
|
4233
|
-
return
|
|
5919
|
+
return join20(deskbuddyHome(), "codex-remote.json");
|
|
4234
5920
|
}
|
|
4235
5921
|
function writeCodexRemote(threads, pid = process.pid, now = Date.now()) {
|
|
4236
5922
|
try {
|
|
@@ -4254,16 +5940,16 @@ import { chmodSync as chmodSync4, rmSync as rmSync9 } from "fs";
|
|
|
4254
5940
|
import { connect, createServer as createServer2 } from "net";
|
|
4255
5941
|
|
|
4256
5942
|
// src/core/wake.ts
|
|
4257
|
-
import { execFileSync as
|
|
5943
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
4258
5944
|
import { createHash as createHash3, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
4259
|
-
import { chmodSync as chmodSync3, closeSync as
|
|
5945
|
+
import { chmodSync as chmodSync3, closeSync as closeSync4, mkdirSync as mkdirSync15, openSync as openSync4, readdirSync as readdirSync5, readFileSync as readFileSync14, readSync as readSync3, renameSync as renameSync11, rmSync as rmSync8, statSync as statSync7, writeFileSync as writeFileSync13 } from "fs";
|
|
4260
5946
|
import { tmpdir as tmpdir2 } from "os";
|
|
4261
|
-
import { join as
|
|
5947
|
+
import { join as join21 } from "path";
|
|
4262
5948
|
function wakeDir(home = deskbuddyHome()) {
|
|
4263
|
-
return
|
|
5949
|
+
return join21(home, "wake");
|
|
4264
5950
|
}
|
|
4265
5951
|
function marksDir(home = deskbuddyHome()) {
|
|
4266
|
-
return
|
|
5952
|
+
return join21(wakeDir(home), "marks");
|
|
4267
5953
|
}
|
|
4268
5954
|
function safeSid(sessionId) {
|
|
4269
5955
|
return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
|
|
@@ -4273,10 +5959,10 @@ function homeTag(home) {
|
|
|
4273
5959
|
}
|
|
4274
5960
|
function wakeChannelPath(platform = process.platform, home = deskbuddyHome()) {
|
|
4275
5961
|
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-wake-${homeTag(home)}`;
|
|
4276
|
-
const primary =
|
|
5962
|
+
const primary = join21(wakeDir(home), "wake.sock");
|
|
4277
5963
|
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
4278
5964
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
4279
|
-
return
|
|
5965
|
+
return join21(tmpdir2(), `deskbuddy-${uid}`, `wake-${homeTag(home)}.sock`);
|
|
4280
5966
|
}
|
|
4281
5967
|
function pidExists(pid) {
|
|
4282
5968
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -4292,7 +5978,7 @@ function writeWakeMark(sessionId, agentPid, now = Date.now()) {
|
|
|
4292
5978
|
if (!safe) return;
|
|
4293
5979
|
try {
|
|
4294
5980
|
mkdirSync15(marksDir(), { recursive: true, mode: 448 });
|
|
4295
|
-
const file =
|
|
5981
|
+
const file = join21(marksDir(), `${safe}.json`);
|
|
4296
5982
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
4297
5983
|
writeFileSync13(tmp, JSON.stringify({ agentPid: agentPid ?? 0, daemonPid: process.pid, at: now }), { encoding: "utf8", mode: 384 });
|
|
4298
5984
|
renameSync11(tmp, file);
|
|
@@ -4303,20 +5989,20 @@ function removeWakeMark(sessionId) {
|
|
|
4303
5989
|
const safe = safeSid(sessionId);
|
|
4304
5990
|
if (!safe) return;
|
|
4305
5991
|
try {
|
|
4306
|
-
rmSync8(
|
|
5992
|
+
rmSync8(join21(marksDir(), `${safe}.json`), { force: true });
|
|
4307
5993
|
} catch {
|
|
4308
5994
|
}
|
|
4309
5995
|
}
|
|
4310
5996
|
function clearWakeMarks() {
|
|
4311
5997
|
let names = [];
|
|
4312
5998
|
try {
|
|
4313
|
-
names =
|
|
5999
|
+
names = readdirSync5(marksDir());
|
|
4314
6000
|
} catch {
|
|
4315
6001
|
return;
|
|
4316
6002
|
}
|
|
4317
6003
|
for (const n of names) {
|
|
4318
6004
|
try {
|
|
4319
|
-
rmSync8(
|
|
6005
|
+
rmSync8(join21(marksDir(), n), { force: true });
|
|
4320
6006
|
} catch {
|
|
4321
6007
|
}
|
|
4322
6008
|
}
|
|
@@ -4325,7 +6011,7 @@ function isWakeMarked(sessionId, alive = pidExists) {
|
|
|
4325
6011
|
const safe = safeSid(sessionId);
|
|
4326
6012
|
if (!safe) return false;
|
|
4327
6013
|
try {
|
|
4328
|
-
const m = JSON.parse(
|
|
6014
|
+
const m = JSON.parse(readFileSync14(join21(marksDir(), `${safe}.json`), "utf8"));
|
|
4329
6015
|
const daemonPid = Number(m?.daemonPid) || 0;
|
|
4330
6016
|
if (!daemonPid || !alive(daemonPid)) return false;
|
|
4331
6017
|
const pid = Number(m?.agentPid) || 0;
|
|
@@ -4338,7 +6024,7 @@ function withWake(states, alive) {
|
|
|
4338
6024
|
return states.map((s) => s.remoteWindow === true || !isWakeMarked(s.sessionId, alive) ? s : { ...s, remoteWindow: true });
|
|
4339
6025
|
}
|
|
4340
6026
|
function wakeSecretPath(home = deskbuddyHome()) {
|
|
4341
|
-
return
|
|
6027
|
+
return join21(wakeDir(home), "secret");
|
|
4342
6028
|
}
|
|
4343
6029
|
function writeWakeSecret(hex, platform = process.platform) {
|
|
4344
6030
|
const file = wakeSecretPath();
|
|
@@ -4347,7 +6033,7 @@ function writeWakeSecret(hex, platform = process.platform) {
|
|
|
4347
6033
|
writeFileSync13(tmp, hex, { encoding: "utf8", mode: 384 });
|
|
4348
6034
|
if (platform === "win32") {
|
|
4349
6035
|
try {
|
|
4350
|
-
|
|
6036
|
+
execFileSync11("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
|
|
4351
6037
|
} catch {
|
|
4352
6038
|
renameSync11(tmp, file);
|
|
4353
6039
|
return "\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u6587\u4EF6\u7684\u6743\u9650\u6CA1\u80FD\u6536\u7D27\uFF08icacls \u5931\u8D25\uFF09";
|
|
@@ -4361,7 +6047,7 @@ function writeWakeSecret(hex, platform = process.platform) {
|
|
|
4361
6047
|
function restrictToUser(file, platform = process.platform) {
|
|
4362
6048
|
if (platform !== "win32") return true;
|
|
4363
6049
|
try {
|
|
4364
|
-
|
|
6050
|
+
execFileSync11("icacls", [file, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
|
|
4365
6051
|
return true;
|
|
4366
6052
|
} catch {
|
|
4367
6053
|
return false;
|
|
@@ -4369,7 +6055,7 @@ function restrictToUser(file, platform = process.platform) {
|
|
|
4369
6055
|
}
|
|
4370
6056
|
function currentUserSid() {
|
|
4371
6057
|
try {
|
|
4372
|
-
const out =
|
|
6058
|
+
const out = execFileSync11("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
|
|
4373
6059
|
const m = /"(S-1-[0-9-]+)"/.exec(out);
|
|
4374
6060
|
if (m) return `*${m[1]}`;
|
|
4375
6061
|
} catch {
|
|
@@ -4404,11 +6090,11 @@ function analyzeTranscript(file) {
|
|
|
4404
6090
|
if (!file) return null;
|
|
4405
6091
|
let fd;
|
|
4406
6092
|
try {
|
|
4407
|
-
const size =
|
|
6093
|
+
const size = statSync7(file).size;
|
|
4408
6094
|
const len = Math.min(size, TRANSCRIPT_TAIL2);
|
|
4409
6095
|
const buf = Buffer.alloc(len);
|
|
4410
|
-
fd =
|
|
4411
|
-
|
|
6096
|
+
fd = openSync4(file, "r");
|
|
6097
|
+
readSync3(fd, buf, 0, len, size - len);
|
|
4412
6098
|
const lines2 = buf.toString("utf8").split("\n");
|
|
4413
6099
|
let seg = false;
|
|
4414
6100
|
let reply = null;
|
|
@@ -4449,7 +6135,7 @@ function analyzeTranscript(file) {
|
|
|
4449
6135
|
} finally {
|
|
4450
6136
|
if (fd !== void 0) {
|
|
4451
6137
|
try {
|
|
4452
|
-
|
|
6138
|
+
closeSync4(fd);
|
|
4453
6139
|
} catch {
|
|
4454
6140
|
}
|
|
4455
6141
|
}
|
|
@@ -4500,7 +6186,7 @@ function replyMac(secret, nonceC, r) {
|
|
|
4500
6186
|
return wakeMac(secret, "r", nonceC, r.instructionId, r.status, r.reason ?? "");
|
|
4501
6187
|
}
|
|
4502
6188
|
function startChannelServer(path, secret, onRequest, platform = process.platform) {
|
|
4503
|
-
return new Promise((
|
|
6189
|
+
return new Promise((resolve7) => {
|
|
4504
6190
|
const server = createServer2((sock) => {
|
|
4505
6191
|
let open = true;
|
|
4506
6192
|
let phase = "hello";
|
|
@@ -4561,7 +6247,7 @@ function startChannelServer(path, secret, onRequest, platform = process.platform
|
|
|
4561
6247
|
};
|
|
4562
6248
|
server.once("error", () => {
|
|
4563
6249
|
restore();
|
|
4564
|
-
|
|
6250
|
+
resolve7(null);
|
|
4565
6251
|
});
|
|
4566
6252
|
server.listen(path, () => {
|
|
4567
6253
|
restore();
|
|
@@ -4573,7 +6259,7 @@ function startChannelServer(path, secret, onRequest, platform = process.platform
|
|
|
4573
6259
|
}
|
|
4574
6260
|
server.on("error", () => {
|
|
4575
6261
|
});
|
|
4576
|
-
|
|
6262
|
+
resolve7(server);
|
|
4577
6263
|
});
|
|
4578
6264
|
});
|
|
4579
6265
|
}
|
|
@@ -4591,9 +6277,9 @@ function closeChannelServer(server, path, platform = process.platform) {
|
|
|
4591
6277
|
}
|
|
4592
6278
|
var FINAL = /* @__PURE__ */ new Set(["started", "rejected", "failed", "not_here"]);
|
|
4593
6279
|
function sendToWindow(path, secret, req2, onReply, opts = {}) {
|
|
4594
|
-
return new Promise((
|
|
6280
|
+
return new Promise((resolve7) => {
|
|
4595
6281
|
if (!secret) {
|
|
4596
|
-
|
|
6282
|
+
resolve7("fallback");
|
|
4597
6283
|
return;
|
|
4598
6284
|
}
|
|
4599
6285
|
let settled = false;
|
|
@@ -4606,13 +6292,13 @@ function sendToWindow(path, secret, req2, onReply, opts = {}) {
|
|
|
4606
6292
|
settled = true;
|
|
4607
6293
|
handled = v === "handled";
|
|
4608
6294
|
clearTimeout(timer);
|
|
4609
|
-
|
|
6295
|
+
resolve7(v);
|
|
4610
6296
|
};
|
|
4611
6297
|
let sock;
|
|
4612
6298
|
try {
|
|
4613
6299
|
sock = connect(path);
|
|
4614
6300
|
} catch {
|
|
4615
|
-
|
|
6301
|
+
resolve7("fallback");
|
|
4616
6302
|
return;
|
|
4617
6303
|
}
|
|
4618
6304
|
const timer = setTimeout(() => {
|
|
@@ -4734,15 +6420,15 @@ function boxMatches(view, body) {
|
|
|
4734
6420
|
}
|
|
4735
6421
|
|
|
4736
6422
|
// src/core/wrapWindows.ts
|
|
4737
|
-
import { chmodSync as chmodSync5, mkdirSync as mkdirSync16, readdirSync as
|
|
6423
|
+
import { chmodSync as chmodSync5, mkdirSync as mkdirSync16, readdirSync as readdirSync6, readFileSync as readFileSync15, renameSync as renameSync12, rmSync as rmSync10, statSync as statSync8, writeFileSync as writeFileSync14 } from "fs";
|
|
4738
6424
|
import { tmpdir as tmpdir3 } from "os";
|
|
4739
|
-
import { join as
|
|
6425
|
+
import { join as join22 } from "path";
|
|
4740
6426
|
var WINDOW_ID_RE = /^[a-f0-9]{32}$/;
|
|
4741
6427
|
function windowsDir() {
|
|
4742
|
-
return
|
|
6428
|
+
return join22(deskbuddyHome(), "windows");
|
|
4743
6429
|
}
|
|
4744
6430
|
function bySessionDir() {
|
|
4745
|
-
return
|
|
6431
|
+
return join22(windowsDir(), "by-session");
|
|
4746
6432
|
}
|
|
4747
6433
|
function safeSid2(sessionId) {
|
|
4748
6434
|
return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
|
|
@@ -4756,7 +6442,7 @@ function atomicWrite(file, text) {
|
|
|
4756
6442
|
renameSync12(tmp, file);
|
|
4757
6443
|
}
|
|
4758
6444
|
function windowFile(windowId) {
|
|
4759
|
-
return
|
|
6445
|
+
return join22(windowsDir(), `${windowId}.json`);
|
|
4760
6446
|
}
|
|
4761
6447
|
function writeWindow(windowId, rec) {
|
|
4762
6448
|
if (!WINDOW_ID_RE.test(windowId)) return;
|
|
@@ -4771,7 +6457,7 @@ function windowSecret(windowId) {
|
|
|
4771
6457
|
function readWindow(windowId) {
|
|
4772
6458
|
if (!WINDOW_ID_RE.test(windowId)) return null;
|
|
4773
6459
|
try {
|
|
4774
|
-
const r = JSON.parse(
|
|
6460
|
+
const r = JSON.parse(readFileSync15(windowFile(windowId), "utf8"));
|
|
4775
6461
|
return r && typeof r.pid === "number" ? r : null;
|
|
4776
6462
|
} catch {
|
|
4777
6463
|
return null;
|
|
@@ -4787,14 +6473,14 @@ function removeWindow(windowId) {
|
|
|
4787
6473
|
}
|
|
4788
6474
|
let names = [];
|
|
4789
6475
|
try {
|
|
4790
|
-
names =
|
|
6476
|
+
names = readdirSync6(bySessionDir());
|
|
4791
6477
|
} catch {
|
|
4792
6478
|
return;
|
|
4793
6479
|
}
|
|
4794
6480
|
for (const n of names) {
|
|
4795
|
-
const f =
|
|
6481
|
+
const f = join22(bySessionDir(), n);
|
|
4796
6482
|
try {
|
|
4797
|
-
if (
|
|
6483
|
+
if (readFileSync15(f, "utf8").trim() === windowId) rmSync10(f, { force: true });
|
|
4798
6484
|
} catch {
|
|
4799
6485
|
}
|
|
4800
6486
|
}
|
|
@@ -4803,19 +6489,19 @@ function readBySession(sessionId) {
|
|
|
4803
6489
|
const safe = safeSid2(sessionId);
|
|
4804
6490
|
if (!safe) return null;
|
|
4805
6491
|
try {
|
|
4806
|
-
const id =
|
|
6492
|
+
const id = readFileSync15(join22(bySessionDir(), safe), "utf8").trim();
|
|
4807
6493
|
return WINDOW_ID_RE.test(id) ? id : null;
|
|
4808
6494
|
} catch {
|
|
4809
6495
|
return null;
|
|
4810
6496
|
}
|
|
4811
6497
|
}
|
|
4812
6498
|
function lastEventFile(windowId) {
|
|
4813
|
-
return
|
|
6499
|
+
return join22(windowsDir(), `${windowId}.last`);
|
|
4814
6500
|
}
|
|
4815
6501
|
function readLastEvent(windowId) {
|
|
4816
6502
|
if (!WINDOW_ID_RE.test(windowId)) return null;
|
|
4817
6503
|
try {
|
|
4818
|
-
const e = JSON.parse(
|
|
6504
|
+
const e = JSON.parse(readFileSync15(lastEventFile(windowId), "utf8"));
|
|
4819
6505
|
return e && typeof e.ev === "string" && typeof e.at === "number" ? { ev: e.ev, sid: String(e.sid ?? ""), at: e.at } : null;
|
|
4820
6506
|
} catch {
|
|
4821
6507
|
return null;
|
|
@@ -4824,16 +6510,16 @@ function readLastEvent(windowId) {
|
|
|
4824
6510
|
function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
|
|
4825
6511
|
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
|
|
4826
6512
|
const name = `${windowId.slice(0, 16)}.sock`;
|
|
4827
|
-
const primary =
|
|
6513
|
+
const primary = join22(home, "win", name);
|
|
4828
6514
|
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
4829
6515
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
4830
|
-
return
|
|
6516
|
+
return join22(tmpdir3(), `deskbuddy-${uid}`, name);
|
|
4831
6517
|
}
|
|
4832
6518
|
function ensureChannelDir(sockPath) {
|
|
4833
|
-
const dir =
|
|
6519
|
+
const dir = join22(sockPath, "..");
|
|
4834
6520
|
try {
|
|
4835
6521
|
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
4836
|
-
const st =
|
|
6522
|
+
const st = statSync8(dir);
|
|
4837
6523
|
if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
|
|
4838
6524
|
chmodSync5(dir, 448);
|
|
4839
6525
|
return true;
|
|
@@ -4852,8 +6538,8 @@ function windowForSession(sessionId) {
|
|
|
4852
6538
|
}
|
|
4853
6539
|
|
|
4854
6540
|
// src/core/launch.ts
|
|
4855
|
-
import { mkdirSync as mkdirSync17, readFileSync as
|
|
4856
|
-
import { join as
|
|
6541
|
+
import { mkdirSync as mkdirSync17, readFileSync as readFileSync16, renameSync as renameSync13, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
6542
|
+
import { join as join23 } from "path";
|
|
4857
6543
|
var RUN_ID_RE = /^[a-f0-9]{32}$/;
|
|
4858
6544
|
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}$/;
|
|
4859
6545
|
var LAUNCH_TOOLS = ["claude_code", "codex", "workbuddy"];
|
|
@@ -4863,10 +6549,10 @@ function validPlan(p) {
|
|
|
4863
6549
|
return true;
|
|
4864
6550
|
}
|
|
4865
6551
|
function launchDir(home = deskbuddyHome()) {
|
|
4866
|
-
return
|
|
6552
|
+
return join23(home, "launch");
|
|
4867
6553
|
}
|
|
4868
6554
|
function planFile(runId) {
|
|
4869
|
-
return
|
|
6555
|
+
return join23(launchDir(), `${runId}.json`);
|
|
4870
6556
|
}
|
|
4871
6557
|
function writeLaunch(runId, plan) {
|
|
4872
6558
|
if (!RUN_ID_RE.test(runId)) throw new Error("bad runId");
|
|
@@ -4879,14 +6565,14 @@ function writeLaunch(runId, plan) {
|
|
|
4879
6565
|
}
|
|
4880
6566
|
function claimLaunch(runId) {
|
|
4881
6567
|
if (!RUN_ID_RE.test(runId)) return null;
|
|
4882
|
-
const claimed =
|
|
6568
|
+
const claimed = join23(launchDir(), `${runId}.${process.pid}.claimed`);
|
|
4883
6569
|
try {
|
|
4884
6570
|
renameSync13(planFile(runId), claimed);
|
|
4885
6571
|
} catch {
|
|
4886
6572
|
return null;
|
|
4887
6573
|
}
|
|
4888
6574
|
try {
|
|
4889
|
-
const p = JSON.parse(
|
|
6575
|
+
const p = JSON.parse(readFileSync16(claimed, "utf8"));
|
|
4890
6576
|
if (!p || typeof p.cwd !== "string" || typeof p.resume !== "string") return null;
|
|
4891
6577
|
if (p.tool === void 0) p.tool = "claude_code";
|
|
4892
6578
|
return validPlan(p) ? p : null;
|
|
@@ -4901,7 +6587,7 @@ function claimLaunch(runId) {
|
|
|
4901
6587
|
}
|
|
4902
6588
|
function launchPending(runId) {
|
|
4903
6589
|
try {
|
|
4904
|
-
|
|
6590
|
+
readFileSync16(planFile(runId));
|
|
4905
6591
|
return true;
|
|
4906
6592
|
} catch {
|
|
4907
6593
|
return false;
|
|
@@ -4909,10 +6595,10 @@ function launchPending(runId) {
|
|
|
4909
6595
|
}
|
|
4910
6596
|
|
|
4911
6597
|
// src/daemon/openWindow.ts
|
|
4912
|
-
import { execFile as execFile4, execFileSync as
|
|
6598
|
+
import { execFile as execFile4, execFileSync as execFileSync12, spawn as spawn4 } from "child_process";
|
|
4913
6599
|
import { chmodSync as chmodSync6, mkdirSync as mkdirSync18, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
4914
6600
|
import { userInfo } from "os";
|
|
4915
|
-
import { join as
|
|
6601
|
+
import { join as join24 } from "path";
|
|
4916
6602
|
function safePath(p) {
|
|
4917
6603
|
return /^(?:[A-Za-z]:[\\/]|\/)/.test(p) && !/[\r\n\0"'`$%;]/.test(p);
|
|
4918
6604
|
}
|
|
@@ -4960,14 +6646,14 @@ var defaultGuiDeps = () => ({
|
|
|
4960
6646
|
env: process.env,
|
|
4961
6647
|
macManager: () => {
|
|
4962
6648
|
try {
|
|
4963
|
-
return
|
|
6649
|
+
return execFileSync12("launchctl", ["managername"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
|
|
4964
6650
|
} catch {
|
|
4965
6651
|
return "";
|
|
4966
6652
|
}
|
|
4967
6653
|
},
|
|
4968
6654
|
macConsoleUser: () => {
|
|
4969
6655
|
try {
|
|
4970
|
-
return
|
|
6656
|
+
return execFileSync12("stat", ["-f", "%Su", "/dev/console"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
|
|
4971
6657
|
} catch {
|
|
4972
6658
|
return "";
|
|
4973
6659
|
}
|
|
@@ -4981,7 +6667,7 @@ var defaultGuiDeps = () => ({
|
|
|
4981
6667
|
},
|
|
4982
6668
|
winSessionId: () => {
|
|
4983
6669
|
try {
|
|
4984
|
-
const out =
|
|
6670
|
+
const out = execFileSync12("tasklist", ["/FI", `PID eq ${process.pid}`, "/FO", "CSV", "/NH"], {
|
|
4985
6671
|
encoding: "utf8",
|
|
4986
6672
|
stdio: ["ignore", "pipe", "ignore"],
|
|
4987
6673
|
windowsHide: true,
|
|
@@ -4998,11 +6684,11 @@ var defaultGuiDeps = () => ({
|
|
|
4998
6684
|
var defaultOpenerDeps = (findWt2) => ({
|
|
4999
6685
|
platform: process.platform,
|
|
5000
6686
|
findWt: findWt2,
|
|
5001
|
-
run: (file, args) => new Promise((
|
|
5002
|
-
execFile4(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) :
|
|
6687
|
+
run: (file, args) => new Promise((resolve7, reject) => {
|
|
6688
|
+
execFile4(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) : resolve7());
|
|
5003
6689
|
}),
|
|
5004
6690
|
spawnDetached: (file, args) => {
|
|
5005
|
-
const c =
|
|
6691
|
+
const c = spawn4(file, args, { detached: true, stdio: "ignore", shell: false, windowsHide: false });
|
|
5006
6692
|
c.on("error", () => {
|
|
5007
6693
|
});
|
|
5008
6694
|
c.unref();
|
|
@@ -5012,7 +6698,7 @@ async function openTerminal(t, d) {
|
|
|
5012
6698
|
checkTarget(t);
|
|
5013
6699
|
if (d.platform === "darwin") {
|
|
5014
6700
|
mkdirSync18(launchDir(t.home), { recursive: true, mode: 448 });
|
|
5015
|
-
const file =
|
|
6701
|
+
const file = join24(launchDir(t.home), `${t.runId}.command`);
|
|
5016
6702
|
writeFileSync16(file, macCommandScript(t), { encoding: "utf8", mode: 448 });
|
|
5017
6703
|
chmodSync6(file, 448);
|
|
5018
6704
|
await d.run("open", ["-a", "Terminal", file]);
|
|
@@ -5032,7 +6718,7 @@ async function openTerminal(t, d) {
|
|
|
5032
6718
|
function removeCommandFile(home, runId) {
|
|
5033
6719
|
if (!RUN_ID_RE.test(runId)) return;
|
|
5034
6720
|
try {
|
|
5035
|
-
rmSync12(
|
|
6721
|
+
rmSync12(join24(launchDir(home), `${runId}.command`), { force: true });
|
|
5036
6722
|
} catch {
|
|
5037
6723
|
}
|
|
5038
6724
|
}
|
|
@@ -5338,7 +7024,7 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
|
|
|
5338
7024
|
} catch {
|
|
5339
7025
|
}
|
|
5340
7026
|
}
|
|
5341
|
-
return new Promise((
|
|
7027
|
+
return new Promise((resolve7) => {
|
|
5342
7028
|
const server = createServer3((sock) => {
|
|
5343
7029
|
sock.on("error", () => {
|
|
5344
7030
|
});
|
|
@@ -5424,36 +7110,36 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
|
|
|
5424
7110
|
};
|
|
5425
7111
|
server.once("error", () => {
|
|
5426
7112
|
restore();
|
|
5427
|
-
|
|
7113
|
+
resolve7(null);
|
|
5428
7114
|
});
|
|
5429
7115
|
server.listen(path, () => {
|
|
5430
7116
|
restore();
|
|
5431
7117
|
server.on("error", () => {
|
|
5432
7118
|
});
|
|
5433
|
-
|
|
7119
|
+
resolve7(server);
|
|
5434
7120
|
});
|
|
5435
7121
|
});
|
|
5436
7122
|
}
|
|
5437
7123
|
|
|
5438
7124
|
// src/core/appended.ts
|
|
5439
|
-
import { chmodSync as chmodSync7, mkdirSync as mkdirSync19, readFileSync as
|
|
5440
|
-
import { join as
|
|
7125
|
+
import { chmodSync as chmodSync7, mkdirSync as mkdirSync19, readFileSync as readFileSync17, renameSync as renameSync14, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "fs";
|
|
7126
|
+
import { join as join25 } from "path";
|
|
5441
7127
|
var APPENDED_MAX_ITEMS = 5;
|
|
5442
7128
|
var APPENDED_TEXT_MAX = 500;
|
|
5443
7129
|
var APPENDED_RESULT_MAX = 800;
|
|
5444
7130
|
var APPENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
5445
7131
|
function appendedDir() {
|
|
5446
|
-
return
|
|
7132
|
+
return join25(deskbuddyHome(), "appended");
|
|
5447
7133
|
}
|
|
5448
7134
|
function appendedPath(sessionId) {
|
|
5449
7135
|
const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
5450
|
-
return safe ?
|
|
7136
|
+
return safe ? join25(appendedDir(), `${safe}.json`) : null;
|
|
5451
7137
|
}
|
|
5452
7138
|
function readAppended(sessionId) {
|
|
5453
7139
|
const file = appendedPath(sessionId);
|
|
5454
7140
|
if (!file) return [];
|
|
5455
7141
|
try {
|
|
5456
|
-
const d = JSON.parse(
|
|
7142
|
+
const d = JSON.parse(readFileSync17(file, "utf8"));
|
|
5457
7143
|
return Array.isArray(d.items) ? d.items : [];
|
|
5458
7144
|
} catch {
|
|
5459
7145
|
return [];
|
|
@@ -5477,13 +7163,13 @@ function writeAppended(sessionId, item) {
|
|
|
5477
7163
|
var RUNNING_TTL_MS = 60 * 60 * 1e3;
|
|
5478
7164
|
function runningPath(sessionId) {
|
|
5479
7165
|
const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
5480
|
-
return safe ?
|
|
7166
|
+
return safe ? join25(deskbuddyHome(), "running", `${safe}.json`) : null;
|
|
5481
7167
|
}
|
|
5482
7168
|
function writeRunning(sessionId, now = Date.now()) {
|
|
5483
7169
|
const file = runningPath(sessionId);
|
|
5484
7170
|
if (!file) return;
|
|
5485
7171
|
try {
|
|
5486
|
-
mkdirSync19(
|
|
7172
|
+
mkdirSync19(join25(deskbuddyHome(), "running"), { recursive: true, mode: 448 });
|
|
5487
7173
|
writeFileSync17(`${file}.tmp`, JSON.stringify({ at: now, pid: process.pid }), { encoding: "utf8", mode: 384 });
|
|
5488
7174
|
renameSync14(`${file}.tmp`, file);
|
|
5489
7175
|
} catch {
|
|
@@ -5544,15 +7230,27 @@ var IDLE_POLL_MS2 = 1e3;
|
|
|
5544
7230
|
var IDLE_QUEUE_MAX_MS = 30 * 60 * 1e3;
|
|
5545
7231
|
var BUSY_STATES2 = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
|
|
5546
7232
|
var CODEX_WATCH_MS = 5 * 1e3;
|
|
7233
|
+
var DUMATE_WATCH_MS = 2500;
|
|
5547
7234
|
function defaultCodexAppServers() {
|
|
5548
7235
|
const cfg = loadConfig();
|
|
5549
7236
|
return cfg.enabledTools.includes("codex") ? discoverAppServers(cfg.codexAppServer) : [];
|
|
5550
7237
|
}
|
|
5551
7238
|
function opencodeReady() {
|
|
5552
|
-
|
|
7239
|
+
const mod = ocPluginModule(installedHookPath());
|
|
7240
|
+
return isOcPluginInstalled(opencodePluginPath()) && existsSync13(mod) && existsSync13(join26(dirname10(mod), "ocTranslate.mjs"));
|
|
5553
7241
|
}
|
|
7242
|
+
var INTERNAL_PROMPT_MARK = "[DeskBuddy]";
|
|
5554
7243
|
function orphanResumeText(summary2) {
|
|
5555
|
-
return
|
|
7244
|
+
return `${INTERNAL_PROMPT_MARK}\uFF08\u4F60\u5728\u8BBE\u5907\u4E0A\u5DF2\u6279\u51C6\uFF1A${summary2}\uFF09\u8BF7\u7EE7\u7EED\u521A\u624D\u88AB\u4E2D\u65AD\u7684\u64CD\u4F5C\u3002`;
|
|
7245
|
+
}
|
|
7246
|
+
function staleAfterRun(read, tool, alive) {
|
|
7247
|
+
const st = read();
|
|
7248
|
+
if (!st || st.tool !== tool || !BUSY_STATES2.has(st.state)) return null;
|
|
7249
|
+
if (st.interactive === true || st.embedded === true) return null;
|
|
7250
|
+
if (st.pid && alive(st.pid, st.pidName)) return null;
|
|
7251
|
+
const again = read();
|
|
7252
|
+
if (!again || again.state !== st.state || again.updatedAt !== st.updatedAt) return null;
|
|
7253
|
+
return st;
|
|
5556
7254
|
}
|
|
5557
7255
|
var ALIVE_REPORT_MS = 60 * 1e3;
|
|
5558
7256
|
var Daemon = class {
|
|
@@ -5564,6 +7262,8 @@ var Daemon = class {
|
|
|
5564
7262
|
#gate;
|
|
5565
7263
|
/** runId → 这次 run 结束的 promise。defer 续跑要等原进程真退出了再起(见 #scheduleResume) */
|
|
5566
7264
|
#runsDone = /* @__PURE__ */ new Map();
|
|
7265
|
+
/** runId → 子进程 pid(按 hook 状态里的进程号认出新建任务建出的会话) */
|
|
7266
|
+
#runPids = /* @__PURE__ */ new Map();
|
|
5567
7267
|
#attempt = 0;
|
|
5568
7268
|
#stopped = false;
|
|
5569
7269
|
#ws = null;
|
|
@@ -5582,6 +7282,11 @@ var Daemon = class {
|
|
|
5582
7282
|
#wakeReport = null;
|
|
5583
7283
|
/** url → 连着它的 CodexWatch。插入顺序 = 发现顺序(DeskBuddy 管的在前) */
|
|
5584
7284
|
#codex = /* @__PURE__ */ new Map();
|
|
7285
|
+
/** 千问办公的选择题 / 授权推设备(spec 2026-10-03 §3.1):hook 发 notify 进来,回填走 App 内置 MCP */
|
|
7286
|
+
#qwenwork;
|
|
7287
|
+
/** 百度搭子(spec 2026-10-03 §3.2):直连 dumate-opencode 的 HTTP,状态上报 + 选择题 / 授权推设备 */
|
|
7288
|
+
#dumate;
|
|
7289
|
+
#dumateTimer = null;
|
|
5585
7290
|
#codexStarted = false;
|
|
5586
7291
|
#codexReportTimer = null;
|
|
5587
7292
|
/** daemon 交出去、还没有结局的指令流程数(开窗 / 写进窗口 / 经 app-server 发一轮)。空闲判定用 */
|
|
@@ -5607,6 +7312,7 @@ var Daemon = class {
|
|
|
5607
7312
|
sendToWindow: deps.sendToWindow ?? ((id, req2, onReply) => sendToWindow(channelPath(id), windowSecret(id), req2, onReply)),
|
|
5608
7313
|
gui: deps.gui ?? (() => guiAvailable(defaultGuiDeps())),
|
|
5609
7314
|
openWindow: deps.openWindow ?? ((runId) => this.#openTerminal(runId)),
|
|
7315
|
+
ancestors: deps.ancestors ?? ((pid) => selfAndAncestors(pid)),
|
|
5610
7316
|
launchable: deps.launchable ?? ((tool) => {
|
|
5611
7317
|
try {
|
|
5612
7318
|
return locate(tool) != null;
|
|
@@ -5652,7 +7358,21 @@ var Daemon = class {
|
|
|
5652
7358
|
log,
|
|
5653
7359
|
now: this.#deps.now
|
|
5654
7360
|
});
|
|
5655
|
-
this.#
|
|
7361
|
+
this.#qwenwork = new QwenWorkBridge({
|
|
7362
|
+
gate: (req2, signal) => this.#coord.onGate(req2, signal),
|
|
7363
|
+
log,
|
|
7364
|
+
enabled: () => loadConfig().enabledTools.includes("qwenwork")
|
|
7365
|
+
});
|
|
7366
|
+
this.#dumate = new DumateWatch({
|
|
7367
|
+
gate: (req2, signal) => this.#coord.onGate(req2, signal),
|
|
7368
|
+
log,
|
|
7369
|
+
enabled: () => loadConfig().enabledTools.includes("dumate"),
|
|
7370
|
+
nodePath: this.#deps.nodePath
|
|
7371
|
+
});
|
|
7372
|
+
this.#gate = new GateServer(
|
|
7373
|
+
(req2, signal) => this.#coord.onGate(req2, signal),
|
|
7374
|
+
(req2) => this.#qwenwork.onNotify(req2)
|
|
7375
|
+
);
|
|
5656
7376
|
}
|
|
5657
7377
|
/** 起本地授权端点。必须在执行任何指令之前起好 —— 否则 gate 连不上,全部 exit 2 */
|
|
5658
7378
|
async startGate() {
|
|
@@ -5666,6 +7386,8 @@ var Daemon = class {
|
|
|
5666
7386
|
this.#codexTick();
|
|
5667
7387
|
this.#codexTimer = setInterval(() => this.#codexTick(), CODEX_WATCH_MS);
|
|
5668
7388
|
this.#codexTimer.unref();
|
|
7389
|
+
this.#dumateTimer = setInterval(() => this.#dumate.tick(), DUMATE_WATCH_MS);
|
|
7390
|
+
this.#dumateTimer.unref();
|
|
5669
7391
|
await this.startWake();
|
|
5670
7392
|
return port;
|
|
5671
7393
|
}
|
|
@@ -5707,7 +7429,7 @@ var Daemon = class {
|
|
|
5707
7429
|
const cfg = loadConfig();
|
|
5708
7430
|
if (!cfg.clientId || !cfg.boundAt || !cfg.enabledTools.includes(tool)) return;
|
|
5709
7431
|
try {
|
|
5710
|
-
await postReport(buildSnapshot(withWake(
|
|
7432
|
+
await postReport(buildSnapshot(withWake(this.#aliveStates(tool)), tool, cfg, this.#deps.now()));
|
|
5711
7433
|
} catch (e) {
|
|
5712
7434
|
log(`\u4E0A\u62A5 ${tool} \u4F1A\u8BDD\u5931\u8D25\uFF1A${e.message}`);
|
|
5713
7435
|
}
|
|
@@ -5780,7 +7502,7 @@ var Daemon = class {
|
|
|
5780
7502
|
const cfg = loadConfig();
|
|
5781
7503
|
if (!cfg.clientId || !cfg.boundAt || !cfg.enabledTools.includes("codex")) return;
|
|
5782
7504
|
try {
|
|
5783
|
-
await postReport(buildSnapshot(
|
|
7505
|
+
await postReport(buildSnapshot(this.#aliveStates("codex"), "codex", cfg, this.#deps.now(), this.#codexLoaded()));
|
|
5784
7506
|
} catch (e) {
|
|
5785
7507
|
log(`\u4E0A\u62A5 codex \u5FEB\u7167\u5931\u8D25\uFF1A${e.message}`);
|
|
5786
7508
|
}
|
|
@@ -5804,13 +7526,23 @@ var Daemon = class {
|
|
|
5804
7526
|
log(`\u4E0A\u62A5\u6700\u8FD1\u76EE\u5F55\u5931\u8D25\uFF1A${e.message}`);
|
|
5805
7527
|
}
|
|
5806
7528
|
}
|
|
7529
|
+
/** 存活会话(顺手剔除死进程、退役没记 pid 又 2 小时无事件的) */
|
|
7530
|
+
#aliveStates(tool) {
|
|
7531
|
+
return readAliveStates(tool, { now: this.#deps.now(), onStale: (s) => log(`\u4F1A\u8BDD ${s.sessionId.slice(0, 8)} ${STALE_NO_PID_LOG}`) });
|
|
7532
|
+
}
|
|
5807
7533
|
/** 按已启用的工具各报一次存活会话快照(readAliveStates 顺手剔除死进程) */
|
|
5808
7534
|
async #reportAlive() {
|
|
5809
7535
|
const cfg = loadConfig();
|
|
5810
7536
|
if (!cfg.clientId || !cfg.boundAt) return;
|
|
5811
7537
|
for (const tool of cfg.enabledTools) {
|
|
5812
7538
|
await postReport(
|
|
5813
|
-
buildSnapshot(
|
|
7539
|
+
buildSnapshot(
|
|
7540
|
+
withWake(this.#aliveStates(tool)),
|
|
7541
|
+
tool,
|
|
7542
|
+
cfg,
|
|
7543
|
+
this.#deps.now(),
|
|
7544
|
+
tool === "codex" ? this.#codexLoaded() : null
|
|
7545
|
+
)
|
|
5814
7546
|
);
|
|
5815
7547
|
}
|
|
5816
7548
|
if (++this.#dirsTick % 10 === 0) await this.#reportDirs();
|
|
@@ -5870,6 +7602,9 @@ var Daemon = class {
|
|
|
5870
7602
|
if (this.#codexReportTimer) clearTimeout(this.#codexReportTimer);
|
|
5871
7603
|
for (const w of this.#codex.values()) w.stop();
|
|
5872
7604
|
this.#codex.clear();
|
|
7605
|
+
this.#qwenwork.stop();
|
|
7606
|
+
if (this.#dumateTimer) clearInterval(this.#dumateTimer);
|
|
7607
|
+
this.#dumate.stop();
|
|
5873
7608
|
if (this.#codexStarted) removeCodexRemote();
|
|
5874
7609
|
this.#ws?.close();
|
|
5875
7610
|
void this.#gate.stop();
|
|
@@ -5974,7 +7709,7 @@ var Daemon = class {
|
|
|
5974
7709
|
if (!this.#seen.remember(ap.instructionId)) return;
|
|
5975
7710
|
this.#seen.save();
|
|
5976
7711
|
log(`\u6536\u5230\u6388\u6743\u51B3\u5B9A ${ap.approvalId} ${ap.decision}`);
|
|
5977
|
-
this.#coord.onDecision(ap.approvalId, ap.decision, ap.deviceName);
|
|
7712
|
+
this.#coord.onDecision(ap.approvalId, ap.decision, ap.deviceName, ap.answers);
|
|
5978
7713
|
return;
|
|
5979
7714
|
}
|
|
5980
7715
|
const cmd = parseCommand(frame);
|
|
@@ -6171,9 +7906,9 @@ var Daemon = class {
|
|
|
6171
7906
|
}
|
|
6172
7907
|
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`);
|
|
6173
7908
|
}
|
|
6174
|
-
const
|
|
7909
|
+
const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6175
7910
|
const claimDeadline = this.#deps.now() + this.#deps.claimWaitMs;
|
|
6176
|
-
while (launchPending(runId) && this.#deps.now() < claimDeadline) await
|
|
7911
|
+
while (launchPending(runId) && this.#deps.now() < claimDeadline) await sleep3(300);
|
|
6177
7912
|
removeCommandFile(deskbuddyHome(), runId);
|
|
6178
7913
|
if (launchPending(runId) && claimLaunch(runId)) return headless(`${Math.round(this.#deps.claimWaitMs / 1e3)} \u79D2\u5185\u7A97\u53E3\u6CA1\u8D77\u6765`);
|
|
6179
7914
|
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`);
|
|
@@ -6211,14 +7946,14 @@ var Daemon = class {
|
|
|
6211
7946
|
found = null;
|
|
6212
7947
|
}
|
|
6213
7948
|
if (found) break;
|
|
6214
|
-
await
|
|
7949
|
+
await sleep3(200);
|
|
6215
7950
|
}
|
|
6216
7951
|
}
|
|
6217
|
-
await
|
|
7952
|
+
await sleep3(50);
|
|
6218
7953
|
if (fb.p) await fb.p;
|
|
6219
7954
|
return;
|
|
6220
7955
|
}
|
|
6221
|
-
await
|
|
7956
|
+
await sleep3(1e3);
|
|
6222
7957
|
}
|
|
6223
7958
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u65B0\u7A97\u53E3\u7684\u901A\u9053\u4E00\u76F4\u6CA1\u5C31\u7EEA`);
|
|
6224
7959
|
this.#report(cmd, sid, "failed", "window", "window_failed");
|
|
@@ -6378,15 +8113,28 @@ var Daemon = class {
|
|
|
6378
8113
|
this.#report(cmd, cmd.sessionId, "rejected", "", target.reason);
|
|
6379
8114
|
return;
|
|
6380
8115
|
}
|
|
8116
|
+
const before = target.sessionId ? this.#runsOfSession(target.sessionId) : [];
|
|
8117
|
+
for (const r of before) this.#coord.supersedeRun(r.runId, target.sessionId);
|
|
6381
8118
|
if (target.sessionId) this.#coord.cancelSession(target.sessionId);
|
|
6382
8119
|
const key = target.sessionId || `new:${cmd.instructionId}`;
|
|
6383
|
-
if (this.#queue.busy(key)) {
|
|
8120
|
+
if (this.#queue.busy(key) || before.length > 0) {
|
|
6384
8121
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD ${key.slice(0, 8)} \u524D\u9762\u8FD8\u6709\u4EFB\u52A1\uFF0C\u6392\u961F`);
|
|
6385
8122
|
this.#report(cmd, target.sessionId, "queued", "headless");
|
|
6386
8123
|
}
|
|
6387
8124
|
this.#queue.enqueue(
|
|
6388
8125
|
key,
|
|
6389
|
-
() =>
|
|
8126
|
+
async () => {
|
|
8127
|
+
for (const r of before) await r.done.catch(() => {
|
|
8128
|
+
});
|
|
8129
|
+
for (let more = this.#runsOfSession(target.sessionId); target.sessionId && more.length > 0; more = this.#runsOfSession(target.sessionId)) {
|
|
8130
|
+
for (const r of more) {
|
|
8131
|
+
this.#coord.supersedeRun(r.runId, target.sessionId);
|
|
8132
|
+
await r.done.catch(() => {
|
|
8133
|
+
});
|
|
8134
|
+
}
|
|
8135
|
+
}
|
|
8136
|
+
await this.#execute(cmd, target.sessionId, target.cwd);
|
|
8137
|
+
},
|
|
6390
8138
|
(e) => {
|
|
6391
8139
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u65F6\u51FA\u9519\uFF1A${e?.stack ?? String(e)}`);
|
|
6392
8140
|
this.#report(cmd, target.sessionId, "failed", "", "exec_failed");
|
|
@@ -6466,7 +8214,7 @@ var Daemon = class {
|
|
|
6466
8214
|
this.#report(cmd, sessionId, "rejected", "", "tool_unsupported");
|
|
6467
8215
|
return;
|
|
6468
8216
|
}
|
|
6469
|
-
log(`\u6267\u884C ${cmd.instructionId} tool=${cmd.tool} ${sessionId ? "resume" : "new"} cwd=\u2026/${
|
|
8217
|
+
log(`\u6267\u884C ${cmd.instructionId} tool=${cmd.tool} ${sessionId ? "resume" : "new"} cwd=\u2026/${basename8(cwd)} len=${cmd.text.length}`);
|
|
6470
8218
|
const result = await this.#run(plan, cmd.tool, runId, sessionId, cwd, () => this.#report(cmd, sessionId, "started", "headless"));
|
|
6471
8219
|
if ("fail" in result) {
|
|
6472
8220
|
log(
|
|
@@ -6478,7 +8226,8 @@ var Daemon = class {
|
|
|
6478
8226
|
if (isDeferred(result.stdout)) log(`\u6307\u4EE4 ${cmd.instructionId} \u505C\u5728\u7B49\u8BBE\u5907\u6388\u6743\u4E0A\uFF0C\u8FDB\u7A0B\u5DF2\u9000\u51FA`);
|
|
6479
8227
|
onResult?.(result);
|
|
6480
8228
|
if (!result.ok) {
|
|
6481
|
-
|
|
8229
|
+
const tail = failureTail(result, cmd.text);
|
|
8230
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u6267\u884C\u5931\u8D25\uFF1A${result.error ?? "\u672A\u77E5"}${tail ? `\uFF1B\u8F93\u51FA\u672B\u5C3E\uFF1A${tail}` : ""}`);
|
|
6482
8231
|
this.#report(cmd, sessionId || result.sessionId || "", "failed", "headless", "exec_failed");
|
|
6483
8232
|
}
|
|
6484
8233
|
if (!sessionId) {
|
|
@@ -6489,6 +8238,53 @@ var Daemon = class {
|
|
|
6489
8238
|
#gateTarget(runId) {
|
|
6490
8239
|
return { runId, nodePath: this.#deps.nodePath, gateScript: this.#deps.gateScript };
|
|
6491
8240
|
}
|
|
8241
|
+
/**
|
|
8242
|
+
* 正在跑、且已知在跑 sessionId 这个会话的后台进程(新建任务起跑时不知道会话号:
|
|
8243
|
+
* 授权关卡请求里带上来(coord.sessionOf),或 hook 写的状态里记的进程号就是它)
|
|
8244
|
+
*/
|
|
8245
|
+
#runsOfSession(sessionId) {
|
|
8246
|
+
if (!sessionId || this.#runsDone.size === 0) return [];
|
|
8247
|
+
let chain = null;
|
|
8248
|
+
const chainOf = () => {
|
|
8249
|
+
if (chain) return chain;
|
|
8250
|
+
let pid;
|
|
8251
|
+
try {
|
|
8252
|
+
pid = readState(sessionId)?.pid;
|
|
8253
|
+
} catch {
|
|
8254
|
+
pid = void 0;
|
|
8255
|
+
}
|
|
8256
|
+
try {
|
|
8257
|
+
chain = typeof pid === "number" && pid > 0 ? this.#deps.ancestors(pid) : [];
|
|
8258
|
+
} catch {
|
|
8259
|
+
chain = typeof pid === "number" && pid > 0 ? [pid] : [];
|
|
8260
|
+
}
|
|
8261
|
+
return chain;
|
|
8262
|
+
};
|
|
8263
|
+
const out = [];
|
|
8264
|
+
for (const [runId, done] of this.#runsDone) {
|
|
8265
|
+
const rp = this.#runPids.get(runId);
|
|
8266
|
+
if (this.#coord.sessionOf(runId) === sessionId || rp !== void 0 && chainOf().includes(rp)) out.push({ runId, done });
|
|
8267
|
+
}
|
|
8268
|
+
return out;
|
|
8269
|
+
}
|
|
8270
|
+
/**
|
|
8271
|
+
* 后台进程跑完了,会话状态却还停在「进行中」(Buglist 161:Codex 续跑后一直 watch)。Codex 的 hook 是 async 的,
|
|
8272
|
+
* exec 退出时 Stop 那次可能没跑完 / 没跑;hook 也可能没认出进程号(pid 空 → 永远不会按进程退出清掉)。
|
|
8273
|
+
* 状态里的进程不在了 / 没记进程号时,按这次的结果落 done / failed。🔴 进程号是个还活着的进程(用户窗口)就不动:
|
|
8274
|
+
* 那是窗口自己的状态,由它的 hook 管;停在等设备授权上(defer)的也不动
|
|
8275
|
+
*/
|
|
8276
|
+
#settleStateAfterRun(sessionId, result, tool) {
|
|
8277
|
+
if (!sessionId || isDeferred(result.stdout)) return;
|
|
8278
|
+
try {
|
|
8279
|
+
const st = staleAfterRun(() => readState(sessionId), tool, (pid, name) => isAlive(pid, name));
|
|
8280
|
+
if (!st) return;
|
|
8281
|
+
const now = this.#deps.now();
|
|
8282
|
+
writeState({ ...st, state: result.ok ? "done" : "failed", updatedAt: Math.max(now, st.updatedAt + 1) });
|
|
8283
|
+
log(`\u4F1A\u8BDD ${sessionId.slice(0, 8)} \u540E\u53F0\u8FDB\u7A0B\u5DF2\u7ED3\u675F\u4F46\u72B6\u6001\u8FD8\u662F ${st.state}\uFF0C\u6309\u7ED3\u679C\u6539\u6210 ${result.ok ? "done" : "failed"}`);
|
|
8284
|
+
void this.#reportAlive();
|
|
8285
|
+
} catch {
|
|
8286
|
+
}
|
|
8287
|
+
}
|
|
6492
8288
|
/** 设备上有了决定、需要续会话。排进该会话的串行队列,和普通指令一样不会并发打同一个会话 */
|
|
6493
8289
|
#scheduleResume(r, kind) {
|
|
6494
8290
|
if (!r.sessionId) {
|
|
@@ -6528,8 +8324,10 @@ var Daemon = class {
|
|
|
6528
8324
|
}
|
|
6529
8325
|
if ("fail" in result) log(`\u7EED\u8DD1\u4F1A\u8BDD ${r.sessionId} \u653E\u5F03\uFF1A${result.fail === "gate_unavailable" ? "\u6388\u6743\u5173\u5361\u4E0D\u53EF\u7528" : `\u672C\u673A\u627E\u4E0D\u5230 ${r.tool} \u7684\u53EF\u6267\u884C\u6587\u4EF6`}`);
|
|
6530
8326
|
else if (isDeferred(result.stdout)) log(`\u4F1A\u8BDD ${r.sessionId} \u53C8\u505C\u5728\u4E86\u4E0B\u4E00\u4E2A\u7B49\u6388\u6743\u7684\u64CD\u4F5C\u4E0A`);
|
|
6531
|
-
else if (!result.ok)
|
|
6532
|
-
|
|
8327
|
+
else if (!result.ok) {
|
|
8328
|
+
const tail = failureTail(result, "");
|
|
8329
|
+
log(`\u7EED\u8DD1\u4F1A\u8BDD ${r.sessionId} \u5931\u8D25\uFF1A${result.error ?? "\u672A\u77E5"}${tail ? `\uFF1B\u8F93\u51FA\u672B\u5C3E\uFF1A${tail}` : ""}`);
|
|
8330
|
+
} else log(`\u7EED\u8DD1\u4F1A\u8BDD ${r.sessionId} \u5B8C\u6210`);
|
|
6533
8331
|
}
|
|
6534
8332
|
/**
|
|
6535
8333
|
* 起进程并在授权协调器里登记这次 run(gate 靠 runId 认人)。
|
|
@@ -6561,13 +8359,19 @@ var Daemon = class {
|
|
|
6561
8359
|
onWaitEnd: () => rc.resumeTimeout()
|
|
6562
8360
|
});
|
|
6563
8361
|
this.#runsDone.set(runId, rc.done);
|
|
8362
|
+
const runPid = rc.pid;
|
|
8363
|
+
if (typeof runPid === "number") this.#runPids.set(runId, runPid);
|
|
8364
|
+
let result = null;
|
|
6564
8365
|
try {
|
|
6565
|
-
|
|
8366
|
+
result = await rc.done;
|
|
8367
|
+
return result;
|
|
6566
8368
|
} finally {
|
|
6567
8369
|
this.#runsDone.delete(runId);
|
|
6568
|
-
|
|
8370
|
+
this.#runPids.delete(runId);
|
|
8371
|
+
const sid = sessionId || this.#coord.sessionOf(runId) || result?.sessionId || "";
|
|
6569
8372
|
this.#coord.endRun(runId);
|
|
6570
8373
|
this.#coord.settleSession(sid, startedAt);
|
|
8374
|
+
if (result) this.#settleStateAfterRun(sid, result, tool);
|
|
6571
8375
|
}
|
|
6572
8376
|
}
|
|
6573
8377
|
};
|
|
@@ -6653,8 +8457,8 @@ async function runDaemonCommand(opts = {}) {
|
|
|
6653
8457
|
}
|
|
6654
8458
|
|
|
6655
8459
|
// src/core/registry.ts
|
|
6656
|
-
import { mkdirSync as mkdirSync20, readFileSync as
|
|
6657
|
-
import { join as
|
|
8460
|
+
import { mkdirSync as mkdirSync20, readFileSync as readFileSync18, writeFileSync as writeFileSync19 } from "fs";
|
|
8461
|
+
import { join as join27 } from "path";
|
|
6658
8462
|
var FALLBACK = {
|
|
6659
8463
|
version: "builtin",
|
|
6660
8464
|
tools: {
|
|
@@ -6739,6 +8543,45 @@ var FALLBACK = {
|
|
|
6739
8543
|
},
|
|
6740
8544
|
lifecycle: { SessionStart: "ignore", SessionEnd: "remove" }
|
|
6741
8545
|
},
|
|
8546
|
+
// 千问办公(QwenWorkCN.app,内核是 App 内置的 Qoder CN CLI,hooks 读 ~/.QwenWorkCN/settings.json)。
|
|
8547
|
+
// PermissionRequest 不触发:授权框只来一条 Notification(permission_prompt)(spec 2026-10-03 §2.1)
|
|
8548
|
+
qwenwork: {
|
|
8549
|
+
format: "cc-hook",
|
|
8550
|
+
processNames: ["QwenWorkCN"],
|
|
8551
|
+
// 工作目录是 ~/.qwenworkcn/workspace/<chatId>,目录名不能当标题
|
|
8552
|
+
titleSources: ["first-prompt", "project"],
|
|
8553
|
+
turnEvents: ["UserPromptSubmit"],
|
|
8554
|
+
events: {
|
|
8555
|
+
UserPromptSubmit: "watch",
|
|
8556
|
+
PreToolUse: "watch",
|
|
8557
|
+
PostToolUse: "watch",
|
|
8558
|
+
Stop: "done"
|
|
8559
|
+
},
|
|
8560
|
+
conditionalEvents: {
|
|
8561
|
+
Notification: [
|
|
8562
|
+
// 实测 message 是 "Tool Bash requires confirmation"(选择题是 "Tool AskUserQuestion requires confirmation"),与服务端注册表一致
|
|
8563
|
+
{ match: "requires confirmation|permission|approve|\u9700\u8981\u786E\u8BA4|\u6388\u6743", state: "ask_approval" },
|
|
8564
|
+
{ match: "waiting for your input|idle|\u95F2\u7F6E", state: "done" }
|
|
8565
|
+
]
|
|
8566
|
+
},
|
|
8567
|
+
lifecycle: { SessionStart: "ignore", SessionEnd: "remove" }
|
|
8568
|
+
},
|
|
8569
|
+
// 百度搭子(DuMate.app,内核是改名的 opencode:dumate-opencode serve)。不往 DuMate 里装任何东西:
|
|
8570
|
+
// daemon 的 DumateWatch 直连它的 HTTP 接口,把事件翻成 cc 同构输入喂 hook.cjs(spec 2026-10-03 §3.2)
|
|
8571
|
+
dumate: {
|
|
8572
|
+
format: "dumate-http",
|
|
8573
|
+
processNames: ["dumate-opencode"],
|
|
8574
|
+
titleSources: ["oc-session-title", "first-prompt"],
|
|
8575
|
+
turnEvents: ["UserPromptSubmit"],
|
|
8576
|
+
events: {
|
|
8577
|
+
UserPromptSubmit: "watch",
|
|
8578
|
+
PreToolUse: "watch",
|
|
8579
|
+
PostToolUse: "watch",
|
|
8580
|
+
Stop: "done",
|
|
8581
|
+
PermissionRequest: "ask_approval"
|
|
8582
|
+
},
|
|
8583
|
+
lifecycle: { SessionEnd: "remove" }
|
|
8584
|
+
},
|
|
6742
8585
|
opencode: {
|
|
6743
8586
|
format: "oc-plugin",
|
|
6744
8587
|
processNames: ["opencode"],
|
|
@@ -6757,7 +8600,7 @@ var FALLBACK = {
|
|
|
6757
8600
|
}
|
|
6758
8601
|
};
|
|
6759
8602
|
function registryCachePath() {
|
|
6760
|
-
return
|
|
8603
|
+
return join27(deskbuddyHome(), "tool-registry.json");
|
|
6761
8604
|
}
|
|
6762
8605
|
function withFallbackTools(registry) {
|
|
6763
8606
|
if (!registry || typeof registry !== "object" || !registry.tools || typeof registry.tools !== "object") return FALLBACK;
|
|
@@ -6765,7 +8608,7 @@ function withFallbackTools(registry) {
|
|
|
6765
8608
|
}
|
|
6766
8609
|
function loadRegistry() {
|
|
6767
8610
|
try {
|
|
6768
|
-
return withFallbackTools(JSON.parse(
|
|
8611
|
+
return withFallbackTools(JSON.parse(readFileSync18(registryCachePath(), "utf8")));
|
|
6769
8612
|
} catch {
|
|
6770
8613
|
return FALLBACK;
|
|
6771
8614
|
}
|
|
@@ -6780,8 +8623,8 @@ async function refreshRegistry() {
|
|
|
6780
8623
|
}
|
|
6781
8624
|
|
|
6782
8625
|
// src/adapters/ccHook.ts
|
|
6783
|
-
import { chmodSync as chmodSync8, existsSync as existsSync14, mkdirSync as mkdirSync21, readFileSync as
|
|
6784
|
-
import { dirname as
|
|
8626
|
+
import { chmodSync as chmodSync8, existsSync as existsSync14, mkdirSync as mkdirSync21, readFileSync as readFileSync19, renameSync as renameSync15, rmSync as rmSync16, statSync as statSync9, writeFileSync as writeFileSync20 } from "fs";
|
|
8627
|
+
import { dirname as dirname11, join as join28 } from "path";
|
|
6785
8628
|
var CC_EVENTS = [
|
|
6786
8629
|
"UserPromptSubmit",
|
|
6787
8630
|
"PreToolUse",
|
|
@@ -6792,7 +8635,7 @@ var CC_EVENTS = [
|
|
|
6792
8635
|
];
|
|
6793
8636
|
function readSettings(path) {
|
|
6794
8637
|
if (!existsSync14(path)) return {};
|
|
6795
|
-
const raw =
|
|
8638
|
+
const raw = readFileSync19(path, "utf8");
|
|
6796
8639
|
if (raw.trim() === "") return {};
|
|
6797
8640
|
try {
|
|
6798
8641
|
return JSON.parse(raw);
|
|
@@ -6801,10 +8644,10 @@ function readSettings(path) {
|
|
|
6801
8644
|
}
|
|
6802
8645
|
}
|
|
6803
8646
|
function writeSettings(path, settings) {
|
|
6804
|
-
mkdirSync21(
|
|
8647
|
+
mkdirSync21(dirname11(path), { recursive: true });
|
|
6805
8648
|
let mode;
|
|
6806
8649
|
try {
|
|
6807
|
-
mode =
|
|
8650
|
+
mode = statSync9(path).mode & 511;
|
|
6808
8651
|
} catch {
|
|
6809
8652
|
mode = void 0;
|
|
6810
8653
|
}
|
|
@@ -6933,8 +8776,8 @@ function retagCcHook(settingsPath, scripts, from, to) {
|
|
|
6933
8776
|
function uninstallCcHook(settingsPath, hookPath) {
|
|
6934
8777
|
const settings = readSettings(settingsPath);
|
|
6935
8778
|
if (!settings.hooks) return;
|
|
6936
|
-
const gatePath =
|
|
6937
|
-
const wakePath =
|
|
8779
|
+
const gatePath = join28(dirname11(hookPath), "gate.cjs");
|
|
8780
|
+
const wakePath = join28(dirname11(hookPath), "wake.cjs");
|
|
6938
8781
|
for (const event of Object.keys(settings.hooks)) {
|
|
6939
8782
|
const groups = settings.hooks[event] ?? [];
|
|
6940
8783
|
const kept = [];
|
|
@@ -6948,14 +8791,14 @@ function uninstallCcHook(settingsPath, hookPath) {
|
|
|
6948
8791
|
}
|
|
6949
8792
|
writeSettings(settingsPath, settings);
|
|
6950
8793
|
try {
|
|
6951
|
-
rmSync16(
|
|
8794
|
+
rmSync16(join28(dirname11(hookPath), "wake", "tokens"), { recursive: true, force: true });
|
|
6952
8795
|
} catch {
|
|
6953
8796
|
}
|
|
6954
8797
|
}
|
|
6955
8798
|
|
|
6956
8799
|
// src/adapters/codexHook.ts
|
|
6957
|
-
import { chmodSync as chmodSync9, existsSync as existsSync15, mkdirSync as mkdirSync22, readFileSync as
|
|
6958
|
-
import { dirname as
|
|
8800
|
+
import { chmodSync as chmodSync9, existsSync as existsSync15, mkdirSync as mkdirSync22, readFileSync as readFileSync20, renameSync as renameSync16, statSync as statSync10, writeFileSync as writeFileSync21 } from "fs";
|
|
8801
|
+
import { dirname as dirname12, join as join29 } from "path";
|
|
6959
8802
|
var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
|
|
6960
8803
|
var CODEX_MARKER_END = "# <<< deskbuddy <<<";
|
|
6961
8804
|
var CODEX_EVENTS = [
|
|
@@ -6968,7 +8811,7 @@ var CODEX_EVENTS = [
|
|
|
6968
8811
|
];
|
|
6969
8812
|
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 DeskBuddy \u5347\u7EA7\uFF08\u542B\u81EA\u52A8\u5347\u7EA7\uFF09\u65B0\u589E hook \u6761\u76EE\u65F6\uFF0CCodex \u53EA\u4F1A\u5C31\u65B0\u6761\u76EE\u518D\u95EE\u4E00\u6B21\uFF0C\u540C\u6837\u9009\u540C\u610F\uFF1B\u539F\u6709\u6761\u76EE\u4E0D\u53D7\u5F71\u54CD\u3001\u72B6\u6001\u7167\u5E38\u4E0A\u62A5\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";
|
|
6970
8813
|
function codexConfigPath() {
|
|
6971
|
-
return
|
|
8814
|
+
return join29(codexHome(), "config.toml");
|
|
6972
8815
|
}
|
|
6973
8816
|
var CODEX_APPENDED_TIMEOUT_S = 3;
|
|
6974
8817
|
function renderBlock(hookPath, events, tool, platform) {
|
|
@@ -6987,10 +8830,10 @@ function renderBlock(hookPath, events, tool, platform) {
|
|
|
6987
8830
|
return lines2.join("\n");
|
|
6988
8831
|
}
|
|
6989
8832
|
function writeAtomic(path, content) {
|
|
6990
|
-
mkdirSync22(
|
|
8833
|
+
mkdirSync22(dirname12(path), { recursive: true });
|
|
6991
8834
|
let mode;
|
|
6992
8835
|
try {
|
|
6993
|
-
mode =
|
|
8836
|
+
mode = statSync10(path).mode & 511;
|
|
6994
8837
|
} catch {
|
|
6995
8838
|
mode = void 0;
|
|
6996
8839
|
}
|
|
@@ -7007,7 +8850,7 @@ function assertSafeToAppend(raw, path) {
|
|
|
7007
8850
|
}
|
|
7008
8851
|
}
|
|
7009
8852
|
function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "codex", platform = process.platform) {
|
|
7010
|
-
const raw = existsSync15(configPath2) ?
|
|
8853
|
+
const raw = existsSync15(configPath2) ? readFileSync20(configPath2, "utf8") : "";
|
|
7011
8854
|
const block = renderBlock(hookPath, events, tool, platform);
|
|
7012
8855
|
if (raw.includes(CODEX_MARKER_BEGIN)) {
|
|
7013
8856
|
const lines2 = raw.split("\n");
|
|
@@ -7025,7 +8868,7 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
|
|
|
7025
8868
|
}
|
|
7026
8869
|
function uninstallCodexHook(configPath2) {
|
|
7027
8870
|
if (!existsSync15(configPath2)) return;
|
|
7028
|
-
const raw =
|
|
8871
|
+
const raw = readFileSync20(configPath2, "utf8");
|
|
7029
8872
|
const lines2 = raw.split("\n");
|
|
7030
8873
|
const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
|
|
7031
8874
|
if (begin === -1) return;
|
|
@@ -7039,11 +8882,11 @@ function uninstallCodexHook(configPath2) {
|
|
|
7039
8882
|
}
|
|
7040
8883
|
|
|
7041
8884
|
// src/commands/tools.ts
|
|
7042
|
-
import { existsSync as existsSync16, readFileSync as
|
|
8885
|
+
import { existsSync as existsSync16, readFileSync as readFileSync21 } from "fs";
|
|
7043
8886
|
function fileContains(path, needle) {
|
|
7044
8887
|
try {
|
|
7045
8888
|
if (!existsSync16(path)) return false;
|
|
7046
|
-
return
|
|
8889
|
+
return readFileSync21(path, "utf8").includes(needle);
|
|
7047
8890
|
} catch {
|
|
7048
8891
|
return false;
|
|
7049
8892
|
}
|
|
@@ -7052,7 +8895,9 @@ var CC_CONFIG_PATHS = {
|
|
|
7052
8895
|
claude_code: claudeSettingsPath,
|
|
7053
8896
|
workbuddy: workbuddySettingsPath,
|
|
7054
8897
|
qoder_cn: qoderCnSettingsPath,
|
|
7055
|
-
qoder: qoderSettingsPath
|
|
8898
|
+
qoder: qoderSettingsPath,
|
|
8899
|
+
// 千问办公只装状态上报:不装 gate.cjs、不装 rewake(选择题 / 授权由 daemon 的 qwenworkBridge 经 App 内置 MCP 回填)
|
|
8900
|
+
qwenwork: qwenworkSettingsPath
|
|
7056
8901
|
};
|
|
7057
8902
|
var WINDOW_GATE_TOOLS = /* @__PURE__ */ new Set(["claude_code", "qoder_cn", "qoder"]);
|
|
7058
8903
|
var ADAPTERS = {
|
|
@@ -7100,6 +8945,26 @@ var ADAPTERS = {
|
|
|
7100
8945
|
installWindowGate: () => true
|
|
7101
8946
|
}
|
|
7102
8947
|
};
|
|
8948
|
+
var DUMATE_NOTICE = " \u767E\u5EA6\u642D\u5B50\u4E0D\u9700\u8981\u6539\u5B83\u7684\u4EFB\u4F55\u914D\u7F6E\uFF1A\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u76F4\u8FDE\u672C\u673A\u7684\u767E\u5EA6\u642D\u5B50\uFF0C\u72B6\u6001\u3001\u9009\u62E9\u9898\u548C\u6388\u6743\u90FD\u4F1A\u63A8\u5230\u8BBE\u5907";
|
|
8949
|
+
ADAPTERS["dumate-http"] = {
|
|
8950
|
+
configPath: (tool) => tool === "dumate" ? configPath() : null,
|
|
8951
|
+
install: () => {
|
|
8952
|
+
},
|
|
8953
|
+
uninstall: () => {
|
|
8954
|
+
},
|
|
8955
|
+
isInstalled: (p) => {
|
|
8956
|
+
try {
|
|
8957
|
+
const enabled = JSON.parse(readFileSync21(p, "utf8")).enabledTools;
|
|
8958
|
+
return Array.isArray(enabled) && enabled.includes("dumate");
|
|
8959
|
+
} catch {
|
|
8960
|
+
return false;
|
|
8961
|
+
}
|
|
8962
|
+
},
|
|
8963
|
+
defaultEvents: [],
|
|
8964
|
+
notice: DUMATE_NOTICE,
|
|
8965
|
+
// 选择题 / 授权由 DumateWatch 推,不另装
|
|
8966
|
+
installWindowGate: () => true
|
|
8967
|
+
};
|
|
7103
8968
|
function adapterFor(registry, tool) {
|
|
7104
8969
|
const format = registry?.tools?.[tool]?.format;
|
|
7105
8970
|
return format && ADAPTERS[format] || null;
|
|
@@ -7120,13 +8985,13 @@ function subscribedEvents(registry, tool, fallback) {
|
|
|
7120
8985
|
}
|
|
7121
8986
|
|
|
7122
8987
|
// src/commands/enable.ts
|
|
7123
|
-
import { dirname as
|
|
8988
|
+
import { dirname as dirname15, join as join32 } from "path";
|
|
7124
8989
|
|
|
7125
8990
|
// src/wrap/shim.ts
|
|
7126
|
-
import { execFileSync as
|
|
7127
|
-
import { chmodSync as chmodSync10, existsSync as existsSync17, mkdirSync as mkdirSync23, readdirSync as
|
|
7128
|
-
import { homedir as
|
|
7129
|
-
import { delimiter as delimiter2, join as
|
|
8991
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
8992
|
+
import { chmodSync as chmodSync10, existsSync as existsSync17, mkdirSync as mkdirSync23, readdirSync as readdirSync7, readFileSync as readFileSync22, rmdirSync, rmSync as rmSync17, writeFileSync as writeFileSync22 } from "fs";
|
|
8993
|
+
import { homedir as homedir8 } from "os";
|
|
8994
|
+
import { delimiter as delimiter2, join as join30, resolve as resolve4 } from "path";
|
|
7130
8995
|
var SHIM_FALLBACK_EXIT = 213;
|
|
7131
8996
|
var WIN_SHIM_ENABLED = false;
|
|
7132
8997
|
var SHIM_COMMANDS = { claude_code: "claude", codex: "codex" };
|
|
@@ -7136,10 +9001,10 @@ var RC_END = "# <<< deskbuddy <<<";
|
|
|
7136
9001
|
var RC_FILES = [".zshrc", ".bashrc"];
|
|
7137
9002
|
var WIN_PENDING = "Windows \u7A97\u53E3\u63A5\u7BA1\u5F85\u9A8C\u8BC1\uFF0C\u6682\u672A\u5F00\u542F";
|
|
7138
9003
|
function shimDir() {
|
|
7139
|
-
return
|
|
9004
|
+
return join30(deskbuddyHome(), "bin");
|
|
7140
9005
|
}
|
|
7141
9006
|
function shimFile(binDir, command, platform = process.platform) {
|
|
7142
|
-
return
|
|
9007
|
+
return join30(binDir, platform === "win32" ? `${command}.cmd` : command);
|
|
7143
9008
|
}
|
|
7144
9009
|
function shQuote3(s) {
|
|
7145
9010
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
@@ -7227,7 +9092,7 @@ function cmdScript(tool, command, node, cli, bin) {
|
|
|
7227
9092
|
].join("\r\n");
|
|
7228
9093
|
}
|
|
7229
9094
|
function rcBlock(binDir, rcHome) {
|
|
7230
|
-
const std =
|
|
9095
|
+
const std = resolve4(rcHome, ".deskbuddy", "bin") === resolve4(binDir);
|
|
7231
9096
|
const dir = std ? "$HOME/.deskbuddy/bin" : binDir.replace(/(["\\$`])/g, "\\$1");
|
|
7232
9097
|
return [
|
|
7233
9098
|
RC_BEGIN,
|
|
@@ -7268,7 +9133,7 @@ function removeRcBlock(content) {
|
|
|
7268
9133
|
return content.slice(0, start) + content.slice(end);
|
|
7269
9134
|
}
|
|
7270
9135
|
function readRc(p) {
|
|
7271
|
-
return
|
|
9136
|
+
return readFileSync22(p).toString("latin1");
|
|
7272
9137
|
}
|
|
7273
9138
|
function writeRc(p, latin1) {
|
|
7274
9139
|
writeFileSync22(p, Buffer.from(latin1, "latin1"));
|
|
@@ -7293,7 +9158,7 @@ function removePathEntry(value, dir) {
|
|
|
7293
9158
|
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)))";
|
|
7294
9159
|
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')";
|
|
7295
9160
|
function psWinPath() {
|
|
7296
|
-
const ps = (script, env) =>
|
|
9161
|
+
const ps = (script, env) => execFileSync13("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], {
|
|
7297
9162
|
encoding: "utf8",
|
|
7298
9163
|
stdio: ["ignore", "pipe", "ignore"],
|
|
7299
9164
|
windowsHide: true,
|
|
@@ -7316,7 +9181,7 @@ function psWinPath() {
|
|
|
7316
9181
|
function defaultShimEnv(nodePath, cliPath) {
|
|
7317
9182
|
const env = process.env;
|
|
7318
9183
|
const isolated = !!env.VITEST || !!env.DESKBUDDY_HOME;
|
|
7319
|
-
const rcHome = env.DESKBUDDY_RC_HOME || (isolated ? null :
|
|
9184
|
+
const rcHome = env.DESKBUDDY_RC_HOME || (isolated ? null : homedir8());
|
|
7320
9185
|
return {
|
|
7321
9186
|
platform: process.platform,
|
|
7322
9187
|
binDir: shimDir(),
|
|
@@ -7359,7 +9224,7 @@ function installShim(tool, e) {
|
|
|
7359
9224
|
} else if (e.rcHome) {
|
|
7360
9225
|
const block = toLatin1(rcBlock(e.binDir, e.rcHome));
|
|
7361
9226
|
for (const name of RC_FILES) {
|
|
7362
|
-
const rc =
|
|
9227
|
+
const rc = join30(e.rcHome, name);
|
|
7363
9228
|
if (!existsSync17(rc)) continue;
|
|
7364
9229
|
const next = addRcBlock(readRc(rc), block);
|
|
7365
9230
|
if (next !== null) writeRc(rc, next);
|
|
@@ -7380,13 +9245,13 @@ function installShim(tool, e) {
|
|
|
7380
9245
|
function ourShims(binDir) {
|
|
7381
9246
|
let names;
|
|
7382
9247
|
try {
|
|
7383
|
-
names =
|
|
9248
|
+
names = readdirSync7(binDir);
|
|
7384
9249
|
} catch {
|
|
7385
9250
|
return [];
|
|
7386
9251
|
}
|
|
7387
9252
|
return names.filter((n) => {
|
|
7388
9253
|
try {
|
|
7389
|
-
return
|
|
9254
|
+
return readFileSync22(join30(binDir, n), "utf8").includes(SHIM_MARKER);
|
|
7390
9255
|
} catch {
|
|
7391
9256
|
return false;
|
|
7392
9257
|
}
|
|
@@ -7398,7 +9263,7 @@ function uninstallShim(tool, e) {
|
|
|
7398
9263
|
let removed = false;
|
|
7399
9264
|
const file = shimFile(e.binDir, command, e.platform);
|
|
7400
9265
|
try {
|
|
7401
|
-
if (existsSync17(file) &&
|
|
9266
|
+
if (existsSync17(file) && readFileSync22(file, "utf8").includes(SHIM_MARKER)) {
|
|
7402
9267
|
rmSync17(file, { force: true });
|
|
7403
9268
|
removed = true;
|
|
7404
9269
|
}
|
|
@@ -7417,7 +9282,7 @@ function uninstallShim(tool, e) {
|
|
|
7417
9282
|
}
|
|
7418
9283
|
} else if (e.rcHome) {
|
|
7419
9284
|
for (const name of RC_FILES) {
|
|
7420
|
-
const rc =
|
|
9285
|
+
const rc = join30(e.rcHome, name);
|
|
7421
9286
|
if (!existsSync17(rc)) continue;
|
|
7422
9287
|
const next = removeRcBlock(readRc(rc));
|
|
7423
9288
|
if (next !== null) {
|
|
@@ -7427,7 +9292,7 @@ function uninstallShim(tool, e) {
|
|
|
7427
9292
|
}
|
|
7428
9293
|
}
|
|
7429
9294
|
try {
|
|
7430
|
-
if (existsSync17(e.binDir) &&
|
|
9295
|
+
if (existsSync17(e.binDir) && readdirSync7(e.binDir).length === 0) rmdirSync(e.binDir);
|
|
7431
9296
|
} catch {
|
|
7432
9297
|
}
|
|
7433
9298
|
if (!removed && touched.length === 0) return { ok: true, message: "" };
|
|
@@ -7439,7 +9304,7 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
|
|
|
7439
9304
|
const file = shimFile(binDir, command, platform);
|
|
7440
9305
|
let installed = false;
|
|
7441
9306
|
try {
|
|
7442
|
-
installed =
|
|
9307
|
+
installed = readFileSync22(file, "utf8").includes(SHIM_MARKER);
|
|
7443
9308
|
} catch {
|
|
7444
9309
|
installed = false;
|
|
7445
9310
|
}
|
|
@@ -7450,12 +9315,12 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
|
|
|
7450
9315
|
}
|
|
7451
9316
|
|
|
7452
9317
|
// src/commands/migrateQoder.ts
|
|
7453
|
-
import { dirname as
|
|
9318
|
+
import { dirname as dirname14, join as join31 } from "path";
|
|
7454
9319
|
|
|
7455
9320
|
// src/core/detect.ts
|
|
7456
9321
|
import { existsSync as existsSync18 } from "fs";
|
|
7457
|
-
import { dirname as
|
|
7458
|
-
var ALL_TOOLS = ["claude_code", "codex", "workbuddy", "qoder_cn", "qoder", "opencode"];
|
|
9322
|
+
import { dirname as dirname13 } from "path";
|
|
9323
|
+
var ALL_TOOLS = ["claude_code", "codex", "workbuddy", "qoder_cn", "qoder", "opencode", "qwenwork", "dumate"];
|
|
7459
9324
|
function toolConfigDir(tool) {
|
|
7460
9325
|
switch (tool) {
|
|
7461
9326
|
case "claude_code":
|
|
@@ -7463,17 +9328,23 @@ function toolConfigDir(tool) {
|
|
|
7463
9328
|
case "codex":
|
|
7464
9329
|
return codexHome();
|
|
7465
9330
|
case "workbuddy":
|
|
7466
|
-
return
|
|
9331
|
+
return dirname13(workbuddySettingsPath());
|
|
7467
9332
|
case "qoder_cn":
|
|
7468
|
-
return
|
|
9333
|
+
return dirname13(qoderCnSettingsPath());
|
|
7469
9334
|
case "qoder":
|
|
7470
|
-
return
|
|
9335
|
+
return dirname13(qoderSettingsPath());
|
|
7471
9336
|
case "opencode":
|
|
7472
|
-
return
|
|
9337
|
+
return dirname13(dirname13(opencodePluginPath()));
|
|
9338
|
+
case "qwenwork":
|
|
9339
|
+
return qwenworkHome();
|
|
7473
9340
|
default:
|
|
7474
9341
|
return null;
|
|
7475
9342
|
}
|
|
7476
9343
|
}
|
|
9344
|
+
function toolDetectDir(tool) {
|
|
9345
|
+
if (tool === "dumate") return qianfanHome();
|
|
9346
|
+
return toolConfigDir(tool);
|
|
9347
|
+
}
|
|
7477
9348
|
var defaultDetectDeps = () => ({
|
|
7478
9349
|
located: (tool) => locate(tool) != null,
|
|
7479
9350
|
exists: existsSync18,
|
|
@@ -7488,7 +9359,7 @@ function detectTools(d = defaultDetectDeps()) {
|
|
|
7488
9359
|
return ALL_TOOLS.filter((tool) => {
|
|
7489
9360
|
if (tool === caller) return true;
|
|
7490
9361
|
if (d.located(tool)) return true;
|
|
7491
|
-
const dir =
|
|
9362
|
+
const dir = toolDetectDir(tool);
|
|
7492
9363
|
return dir != null && d.exists(dir);
|
|
7493
9364
|
});
|
|
7494
9365
|
}
|
|
@@ -7498,7 +9369,7 @@ function migrateLegacyQoder(out, detect = () => detectTools()) {
|
|
|
7498
9369
|
try {
|
|
7499
9370
|
const cfg = loadConfig();
|
|
7500
9371
|
const hookPath = installedHookPath();
|
|
7501
|
-
const scripts = [hookPath,
|
|
9372
|
+
const scripts = [hookPath, join31(dirname14(hookPath), "gate.cjs")];
|
|
7502
9373
|
if (!retagCcHook(qoderCnSettingsPath(), scripts, "qoder", "qoder_cn")) return false;
|
|
7503
9374
|
const hadQoder = cfg.enabledTools.includes("qoder");
|
|
7504
9375
|
let intl = false;
|
|
@@ -7549,7 +9420,8 @@ function installShims(tools, out, shim) {
|
|
|
7549
9420
|
const r = installShim(tool, shim);
|
|
7550
9421
|
if (r.message) out(r.ok ? `\u2705 ${r.message}` : `\u26A0\uFE0F ${r.message}`);
|
|
7551
9422
|
if (r.ok && tool === "claude_code" && (!loadPty() || !loadHeadless())) {
|
|
7552
|
-
|
|
9423
|
+
const why = loadPty() ? "@xterm/headless \u6CA1\u88C5\u4E0A" : ptyMissingReason();
|
|
9424
|
+
out(`\u26A0\uFE0F ${why}\uFF1A\u7A97\u53E3\u63A5\u7BA1\u5148\u964D\u7EA7\u4E3A\u76F4\u63A5\u8FD0\u884C\uFF08claude \u7167\u5E38\u80FD\u7528\uFF0C\u53EA\u662F\u8BBE\u5907\u56DE\u590D\u5199\u4E0D\u8FDB\u7A97\u53E3\uFF09`);
|
|
7553
9425
|
}
|
|
7554
9426
|
} catch (e) {
|
|
7555
9427
|
out(`\u26A0\uFE0F \u7A97\u53E3\u63A5\u7BA1\u6CA1\u88C5\u4E0A\uFF1A${e.message}\uFF08\u4E0D\u5F71\u54CD\u5176\u5B83\u529F\u80FD\uFF09`);
|
|
@@ -7590,14 +9462,14 @@ function installTools(tools, out, shim = defaultShimEnvForInstall()) {
|
|
|
7590
9462
|
installed.push(tool);
|
|
7591
9463
|
out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
|
|
7592
9464
|
try {
|
|
7593
|
-
if (adapter.installWindowGate?.(configPath2,
|
|
9465
|
+
if (adapter.installWindowGate?.(configPath2, join32(dirname15(hookPath), "gate.cjs"), tool)) {
|
|
7594
9466
|
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`);
|
|
7595
9467
|
}
|
|
7596
9468
|
} catch (e) {
|
|
7597
9469
|
out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
|
|
7598
9470
|
}
|
|
7599
9471
|
try {
|
|
7600
|
-
if (adapter.installRewake?.(configPath2,
|
|
9472
|
+
if (adapter.installRewake?.(configPath2, join32(dirname15(hookPath), "wake.cjs"), tool)) {
|
|
7601
9473
|
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`);
|
|
7602
9474
|
}
|
|
7603
9475
|
} catch (e) {
|
|
@@ -7699,27 +9571,27 @@ async function runDisable(tools, opts = {}) {
|
|
|
7699
9571
|
|
|
7700
9572
|
// src/commands/install.ts
|
|
7701
9573
|
import { mkdirSync as mkdirSync24, rmSync as rmSync18, writeFileSync as writeFileSync23 } from "fs";
|
|
7702
|
-
import { join as
|
|
9574
|
+
import { join as join33 } from "path";
|
|
7703
9575
|
|
|
7704
9576
|
// src/commands/setup.ts
|
|
7705
9577
|
import qrcode from "qrcode-terminal";
|
|
7706
9578
|
|
|
7707
9579
|
// src/commands/remoteCwd.ts
|
|
7708
|
-
import { existsSync as existsSync19, statSync as
|
|
9580
|
+
import { existsSync as existsSync19, statSync as statSync11 } from "fs";
|
|
7709
9581
|
import { createInterface } from "readline";
|
|
7710
|
-
import { homedir as
|
|
7711
|
-
import { isAbsolute as isAbsolute2, resolve as
|
|
7712
|
-
function normalizePath(raw, home =
|
|
9582
|
+
import { homedir as homedir9 } from "os";
|
|
9583
|
+
import { isAbsolute as isAbsolute2, resolve as resolve5 } from "path";
|
|
9584
|
+
function normalizePath(raw, home = homedir9()) {
|
|
7713
9585
|
const s = (raw || "").trim();
|
|
7714
9586
|
if (!s) return null;
|
|
7715
|
-
const expanded = s === "~" ? home : s.startsWith("~/") || process.platform === "win32" && s.startsWith("~\\") ?
|
|
7716
|
-
const abs = isAbsolute2(expanded) ? expanded :
|
|
9587
|
+
const expanded = s === "~" ? home : s.startsWith("~/") || process.platform === "win32" && s.startsWith("~\\") ? resolve5(home, s.slice(2)) : s;
|
|
9588
|
+
const abs = isAbsolute2(expanded) ? expanded : resolve5(process.cwd(), expanded);
|
|
7717
9589
|
return abs;
|
|
7718
9590
|
}
|
|
7719
9591
|
function checkPath(abs) {
|
|
7720
9592
|
if (!existsSync19(abs)) return "missing";
|
|
7721
9593
|
try {
|
|
7722
|
-
return
|
|
9594
|
+
return statSync11(abs).isDirectory() ? "ok" : "not_a_dir";
|
|
7723
9595
|
} catch {
|
|
7724
9596
|
return "missing";
|
|
7725
9597
|
}
|
|
@@ -7828,7 +9700,7 @@ function parseApiBase(argv) {
|
|
|
7828
9700
|
}
|
|
7829
9701
|
return value;
|
|
7830
9702
|
}
|
|
7831
|
-
function
|
|
9703
|
+
function sleep2(ms) {
|
|
7832
9704
|
return new Promise((r) => setTimeout(r, ms));
|
|
7833
9705
|
}
|
|
7834
9706
|
function stdout3(line) {
|
|
@@ -7851,7 +9723,7 @@ async function runSetup(opts = {}) {
|
|
|
7851
9723
|
await pollForBinding(
|
|
7852
9724
|
authCode,
|
|
7853
9725
|
{ intervalSec: interval },
|
|
7854
|
-
{ authToken: (code) => api.authToken(code), sleep, now: () => Date.now() }
|
|
9726
|
+
{ authToken: (code) => api.authToken(code), sleep: sleep2, now: () => Date.now() }
|
|
7855
9727
|
);
|
|
7856
9728
|
markBound();
|
|
7857
9729
|
await refreshRegistry();
|
|
@@ -7889,7 +9761,9 @@ var TOOL_LABELS = {
|
|
|
7889
9761
|
workbuddy: "WorkBuddy",
|
|
7890
9762
|
qoder_cn: "Qoder CN",
|
|
7891
9763
|
qoder: "Qoder",
|
|
7892
|
-
opencode: "opencode"
|
|
9764
|
+
opencode: "opencode",
|
|
9765
|
+
qwenwork: "\u5343\u95EE\u529E\u516C",
|
|
9766
|
+
dumate: "\u767E\u5EA6\u642D\u5B50"
|
|
7893
9767
|
};
|
|
7894
9768
|
function checkApiBase(raw, env = process.env) {
|
|
7895
9769
|
const norm3 = raw.replace(/\/+$/, "");
|
|
@@ -7924,7 +9798,7 @@ function inSandbox(env) {
|
|
|
7924
9798
|
}
|
|
7925
9799
|
function precheckWritable(dirs) {
|
|
7926
9800
|
for (const dir of dirs) {
|
|
7927
|
-
const probe =
|
|
9801
|
+
const probe = join33(dir, `.deskbuddy-probe-${process.pid}`);
|
|
7928
9802
|
try {
|
|
7929
9803
|
mkdirSync24(dir, { recursive: true });
|
|
7930
9804
|
writeFileSync23(probe, "");
|
|
@@ -8062,11 +9936,11 @@ function stdout4(line) {
|
|
|
8062
9936
|
process.stdout.write(line + "\n");
|
|
8063
9937
|
}
|
|
8064
9938
|
function askYesNo(question) {
|
|
8065
|
-
return new Promise((
|
|
9939
|
+
return new Promise((resolve7) => {
|
|
8066
9940
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
8067
9941
|
rl.question(question, (answer) => {
|
|
8068
9942
|
rl.close();
|
|
8069
|
-
|
|
9943
|
+
resolve7(/^(y|yes)$/i.test(answer.trim()));
|
|
8070
9944
|
});
|
|
8071
9945
|
});
|
|
8072
9946
|
}
|
|
@@ -8151,7 +10025,7 @@ function formatStatus(input) {
|
|
|
8151
10025
|
lines2.push(` ${pad("hook \u7248\u672C", 10)}${versionNote}`);
|
|
8152
10026
|
if (input.windowWrap) {
|
|
8153
10027
|
const w = input.windowWrap;
|
|
8154
|
-
const text = w.winPending ? WIN_PENDING : !w.installed ? "\u672A\u5B89\u88C5 \u2014\u2014 \u91CD\u8DD1 deskbuddy enable claude_code" : !w.inPath ? `\u672A\u751F\u6548\uFF08\u5F53\u524D shell \u7684 PATH \u91CC\u6CA1\u6709 ${w.binDir}\uFF0C\u65B0\u5F00\u4E00\u4E2A\u7EC8\u7AEF\u518D\u770B\uFF09` : !w.ptyOk ?
|
|
10028
|
+
const text = w.winPending ? WIN_PENDING : !w.installed ? "\u672A\u5B89\u88C5 \u2014\u2014 \u91CD\u8DD1 deskbuddy enable claude_code" : !w.inPath ? `\u672A\u751F\u6548\uFF08\u5F53\u524D shell \u7684 PATH \u91CC\u6CA1\u6709 ${w.binDir}\uFF0C\u65B0\u5F00\u4E00\u4E2A\u7EC8\u7AEF\u518D\u770B\uFF09` : !w.ptyOk ? `\u5DF2\u5F00\u542F\uFF0C\u4F46 ${w.ptyWhy ?? "node-pty \u6CA1\u88C5\u4E0A"}\uFF0C\u6682\u65F6\u964D\u7EA7\u4E3A\u76F4\u63A5\u8FD0\u884C` : "\u5DF2\u5F00\u542F\uFF08\u7EC8\u7AEF\u91CC\u6572 claude \u4F1A\u7ECF DeskBuddy \u6253\u5F00\uFF0C\u8BBE\u5907\u56DE\u590D\u80FD\u5199\u8FDB\u7A97\u53E3\uFF09";
|
|
8155
10029
|
lines2.push(` ${pad("\u7A97\u53E3\u63A5\u7BA1", 10)}${text}`);
|
|
8156
10030
|
}
|
|
8157
10031
|
if (input.codexWrap) {
|
|
@@ -8196,7 +10070,7 @@ function collectStatus(now = Date.now()) {
|
|
|
8196
10070
|
installed,
|
|
8197
10071
|
notice: installed ? adapter.notice : void 0
|
|
8198
10072
|
});
|
|
8199
|
-
sessions.push(...readAliveStates(tool));
|
|
10073
|
+
sessions.push(...readAliveStates(tool, { now }));
|
|
8200
10074
|
}
|
|
8201
10075
|
const wrapTool = config.enabledTools.find((t) => t === "claude_code" && SHIM_COMMANDS[t]);
|
|
8202
10076
|
let windowWrap;
|
|
@@ -8208,7 +10082,15 @@ function collectStatus(now = Date.now()) {
|
|
|
8208
10082
|
if (wrapTool) {
|
|
8209
10083
|
const binDir = shimDir();
|
|
8210
10084
|
const st = shimState(wrapTool, binDir, process.env.PATH ?? "");
|
|
8211
|
-
|
|
10085
|
+
const pty = !!loadPty();
|
|
10086
|
+
const headless = !!loadHeadless();
|
|
10087
|
+
windowWrap = {
|
|
10088
|
+
...st,
|
|
10089
|
+
binDir,
|
|
10090
|
+
ptyOk: pty && headless,
|
|
10091
|
+
ptyWhy: !pty ? ptyMissingReason() : !headless ? "@xterm/headless \u6CA1\u88C5\u4E0A" : void 0,
|
|
10092
|
+
winPending: process.platform === "win32" && !WIN_SHIM_ENABLED
|
|
10093
|
+
};
|
|
8212
10094
|
}
|
|
8213
10095
|
return {
|
|
8214
10096
|
windowWrap,
|
|
@@ -8277,8 +10159,8 @@ async function runAutoSwitch(opts) {
|
|
|
8277
10159
|
|
|
8278
10160
|
// src/wrap/run.ts
|
|
8279
10161
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
8280
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync26, statSync as
|
|
8281
|
-
import { join as
|
|
10162
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync26, statSync as statSync13, truncateSync as truncateSync3 } from "fs";
|
|
10163
|
+
import { join as join36 } from "path";
|
|
8282
10164
|
import { StringDecoder } from "string_decoder";
|
|
8283
10165
|
|
|
8284
10166
|
// src/wrap/injector.ts
|
|
@@ -8526,10 +10408,10 @@ var Injector = class {
|
|
|
8526
10408
|
};
|
|
8527
10409
|
|
|
8528
10410
|
// src/wrap/passthrough.ts
|
|
8529
|
-
import { spawn as
|
|
8530
|
-
import { realpathSync as
|
|
10411
|
+
import { spawn as spawn5 } from "child_process";
|
|
10412
|
+
import { realpathSync as realpathSync4 } from "fs";
|
|
8531
10413
|
import { constants as constants2 } from "os";
|
|
8532
|
-
import { dirname as
|
|
10414
|
+
import { dirname as dirname16, resolve as resolve6 } from "path";
|
|
8533
10415
|
var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
8534
10416
|
"agents",
|
|
8535
10417
|
"attach",
|
|
@@ -8580,12 +10462,12 @@ function passthroughReason(i) {
|
|
|
8580
10462
|
return null;
|
|
8581
10463
|
}
|
|
8582
10464
|
function norm2(p, win) {
|
|
8583
|
-
const r =
|
|
10465
|
+
const r = resolve6(p).replace(/[\\/]+$/, "");
|
|
8584
10466
|
return win ? r.replace(/\//g, "\\").toLowerCase() : r;
|
|
8585
10467
|
}
|
|
8586
10468
|
function real(p) {
|
|
8587
10469
|
try {
|
|
8588
|
-
return
|
|
10470
|
+
return realpathSync4(p);
|
|
8589
10471
|
} catch {
|
|
8590
10472
|
return p;
|
|
8591
10473
|
}
|
|
@@ -8593,7 +10475,7 @@ function real(p) {
|
|
|
8593
10475
|
function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
|
|
8594
10476
|
const win = (d.platform ?? process.platform) === "win32";
|
|
8595
10477
|
const skipKeys = /* @__PURE__ */ new Set([norm2(skip, win), norm2(real(skip), win)]);
|
|
8596
|
-
const inSkip = (file) => skipKeys.has(norm2(
|
|
10478
|
+
const inSkip = (file) => skipKeys.has(norm2(dirname16(file), win)) || skipKeys.has(norm2(dirname16(real(file)), win));
|
|
8597
10479
|
const dd = { ...d, skipDirs: [...d.skipDirs ?? [], skip] };
|
|
8598
10480
|
const name = SHIM_COMMANDS[tool];
|
|
8599
10481
|
if (!name) {
|
|
@@ -8622,7 +10504,7 @@ function runDirect(real2, args, env = process.env) {
|
|
|
8622
10504
|
return new Promise((done) => {
|
|
8623
10505
|
let child;
|
|
8624
10506
|
try {
|
|
8625
|
-
child =
|
|
10507
|
+
child = spawn5(real2.file, [...real2.prefixArgs, ...args], {
|
|
8626
10508
|
stdio: "inherit",
|
|
8627
10509
|
// 🔴 再经过垫片也直接放行,不会绕回来
|
|
8628
10510
|
env: withoutShimFlag({ ...env, DESKBUDDY_WRAPPED: "1" }),
|
|
@@ -8672,21 +10554,21 @@ function runDirect(real2, args, env = process.env) {
|
|
|
8672
10554
|
}
|
|
8673
10555
|
|
|
8674
10556
|
// src/wrap/codexLaunch.ts
|
|
8675
|
-
import { spawn as
|
|
8676
|
-
import { closeSync as
|
|
10557
|
+
import { spawn as spawn6 } from "child_process";
|
|
10558
|
+
import { closeSync as closeSync5, mkdirSync as mkdirSync25, openSync as openSync5, readdirSync as readdirSync8, readFileSync as readFileSync23, rmSync as rmSync20, statSync as statSync12, truncateSync as truncateSync2, writeFileSync as writeFileSync24, renameSync as renameSync17 } from "fs";
|
|
8677
10559
|
import { request } from "http";
|
|
8678
10560
|
import { createServer as createServer4 } from "net";
|
|
8679
|
-
import { join as
|
|
10561
|
+
import { join as join34 } from "path";
|
|
8680
10562
|
|
|
8681
10563
|
// src/core/codexServer.ts
|
|
8682
|
-
import { execFileSync as
|
|
10564
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
8683
10565
|
function isAppServerArgs(args, url) {
|
|
8684
10566
|
return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
|
|
8685
10567
|
}
|
|
8686
10568
|
function processArgs(pid) {
|
|
8687
10569
|
if (process.platform === "win32") return null;
|
|
8688
10570
|
try {
|
|
8689
|
-
const out =
|
|
10571
|
+
const out = execFileSync14("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
|
|
8690
10572
|
encoding: "utf8",
|
|
8691
10573
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8692
10574
|
});
|
|
@@ -8844,10 +10726,10 @@ async function runCodexWindow(real2, args, d) {
|
|
|
8844
10726
|
}
|
|
8845
10727
|
var MAX_LOG_BYTES2 = 1024 * 1024;
|
|
8846
10728
|
function appServerLogPath() {
|
|
8847
|
-
return
|
|
10729
|
+
return join34(deskbuddyHome(), "codex-app-server.log");
|
|
8848
10730
|
}
|
|
8849
10731
|
function windowsDir2() {
|
|
8850
|
-
return
|
|
10732
|
+
return join34(deskbuddyHome(), "codex-windows");
|
|
8851
10733
|
}
|
|
8852
10734
|
function pidAlive2(pid) {
|
|
8853
10735
|
try {
|
|
@@ -8860,7 +10742,7 @@ function pidAlive2(pid) {
|
|
|
8860
10742
|
function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
|
|
8861
10743
|
let pid = 0;
|
|
8862
10744
|
try {
|
|
8863
|
-
pid = Number(
|
|
10745
|
+
pid = Number(readFileSync23(pidFile, "utf8").trim());
|
|
8864
10746
|
} catch {
|
|
8865
10747
|
return false;
|
|
8866
10748
|
}
|
|
@@ -8870,25 +10752,25 @@ function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
|
|
|
8870
10752
|
}
|
|
8871
10753
|
function probeReady(url) {
|
|
8872
10754
|
const port = Number(url.slice(url.lastIndexOf(":") + 1));
|
|
8873
|
-
return new Promise((
|
|
10755
|
+
return new Promise((resolve7) => {
|
|
8874
10756
|
const req2 = request({ host: "127.0.0.1", port, path: "/readyz", method: "GET", timeout: 800 }, (res) => {
|
|
8875
10757
|
res.resume();
|
|
8876
|
-
|
|
10758
|
+
resolve7((res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300);
|
|
8877
10759
|
});
|
|
8878
10760
|
req2.on("timeout", () => req2.destroy());
|
|
8879
|
-
req2.on("error", () =>
|
|
10761
|
+
req2.on("error", () => resolve7(false));
|
|
8880
10762
|
req2.end();
|
|
8881
10763
|
});
|
|
8882
10764
|
}
|
|
8883
10765
|
function freePort() {
|
|
8884
|
-
return new Promise((
|
|
10766
|
+
return new Promise((resolve7, reject) => {
|
|
8885
10767
|
const srv = createServer4();
|
|
8886
10768
|
srv.unref();
|
|
8887
10769
|
srv.on("error", reject);
|
|
8888
10770
|
srv.listen(0, "127.0.0.1", () => {
|
|
8889
10771
|
const addr = srv.address();
|
|
8890
10772
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
8891
|
-
srv.close(() => port ?
|
|
10773
|
+
srv.close(() => port ? resolve7(port) : reject(new Error("no port")));
|
|
8892
10774
|
});
|
|
8893
10775
|
});
|
|
8894
10776
|
}
|
|
@@ -8896,14 +10778,14 @@ function startServer(real2, url, env, cwd) {
|
|
|
8896
10778
|
const logPath2 = appServerLogPath();
|
|
8897
10779
|
mkdirSync25(deskbuddyHome(), { recursive: true });
|
|
8898
10780
|
try {
|
|
8899
|
-
if (
|
|
10781
|
+
if (statSync12(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
|
|
8900
10782
|
} catch {
|
|
8901
10783
|
}
|
|
8902
|
-
const fd =
|
|
10784
|
+
const fd = openSync5(logPath2, "a");
|
|
8903
10785
|
try {
|
|
8904
10786
|
const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
|
|
8905
10787
|
delete childEnv2.DESKBUDDY_SHIM;
|
|
8906
|
-
const child =
|
|
10788
|
+
const child = spawn6(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], {
|
|
8907
10789
|
detached: true,
|
|
8908
10790
|
stdio: ["ignore", fd, fd],
|
|
8909
10791
|
cwd,
|
|
@@ -8914,7 +10796,7 @@ function startServer(real2, url, env, cwd) {
|
|
|
8914
10796
|
child.unref();
|
|
8915
10797
|
return child.pid ?? null;
|
|
8916
10798
|
} finally {
|
|
8917
|
-
|
|
10799
|
+
closeSync5(fd);
|
|
8918
10800
|
}
|
|
8919
10801
|
}
|
|
8920
10802
|
function killServer(pid) {
|
|
@@ -8928,7 +10810,7 @@ function killOwnServer(pid, url) {
|
|
|
8928
10810
|
if (a && isAppServerArgs(a, url)) killServer(pid);
|
|
8929
10811
|
}
|
|
8930
10812
|
function recordPath(rec) {
|
|
8931
|
-
return
|
|
10813
|
+
return join34(windowsDir2(), `${rec.server}.json`);
|
|
8932
10814
|
}
|
|
8933
10815
|
function writeRecord(rec) {
|
|
8934
10816
|
try {
|
|
@@ -8948,15 +10830,15 @@ function removeRecord(rec) {
|
|
|
8948
10830
|
function reapWindows(dir = windowsDir2(), alive = pidAlive2, args = processArgs, kill = killServer) {
|
|
8949
10831
|
let names = [];
|
|
8950
10832
|
try {
|
|
8951
|
-
names =
|
|
10833
|
+
names = readdirSync8(dir).filter((n) => n.endsWith(".json"));
|
|
8952
10834
|
} catch {
|
|
8953
10835
|
return;
|
|
8954
10836
|
}
|
|
8955
10837
|
for (const n of names) {
|
|
8956
|
-
const p =
|
|
10838
|
+
const p = join34(dir, n);
|
|
8957
10839
|
let rec;
|
|
8958
10840
|
try {
|
|
8959
|
-
rec = JSON.parse(
|
|
10841
|
+
rec = JSON.parse(readFileSync23(p, "utf8"));
|
|
8960
10842
|
} catch {
|
|
8961
10843
|
rmSync20(p, { force: true });
|
|
8962
10844
|
continue;
|
|
@@ -8976,7 +10858,7 @@ function reapWindows(dir = windowsDir2(), alive = pidAlive2, args = processArgs,
|
|
|
8976
10858
|
function launchLog(line) {
|
|
8977
10859
|
try {
|
|
8978
10860
|
mkdirSync25(deskbuddyHome(), { recursive: true });
|
|
8979
|
-
writeFileSync24(
|
|
10861
|
+
writeFileSync24(join34(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
|
|
8980
10862
|
`, { flag: "a" });
|
|
8981
10863
|
} catch {
|
|
8982
10864
|
}
|
|
@@ -9000,15 +10882,15 @@ function defaultCodexWindowDeps(base) {
|
|
|
9000
10882
|
|
|
9001
10883
|
// src/wrap/promptLaunch.ts
|
|
9002
10884
|
import { randomBytes as randomBytes8 } from "crypto";
|
|
9003
|
-
import { homedir as
|
|
9004
|
-
import { join as
|
|
10885
|
+
import { homedir as homedir10 } from "os";
|
|
10886
|
+
import { join as join35 } from "path";
|
|
9005
10887
|
var FIRST_LINE_WAIT_MS2 = 2 * 60 * 1e3;
|
|
9006
10888
|
function promptArgs(text) {
|
|
9007
10889
|
return text ? ["--", prepare(text)] : [];
|
|
9008
10890
|
}
|
|
9009
10891
|
function promptEnv(tool, env) {
|
|
9010
10892
|
const out = { ...env };
|
|
9011
|
-
if (tool === "workbuddy" && !out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR =
|
|
10893
|
+
if (tool === "workbuddy" && !out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR = join35(homedir10(), ".workbuddy");
|
|
9012
10894
|
return out;
|
|
9013
10895
|
}
|
|
9014
10896
|
async function runPromptLaunch(tool, windowId, cwd, d) {
|
|
@@ -9065,7 +10947,7 @@ function receiveFirstLine(windowId, tool, cwd, opts = {}) {
|
|
|
9065
10947
|
cleanup(null);
|
|
9066
10948
|
return Promise.resolve(null);
|
|
9067
10949
|
}
|
|
9068
|
-
return new Promise((
|
|
10950
|
+
return new Promise((resolve7) => {
|
|
9069
10951
|
let server = null;
|
|
9070
10952
|
let done = false;
|
|
9071
10953
|
let firstId = "";
|
|
@@ -9076,7 +10958,7 @@ function receiveFirstLine(windowId, tool, cwd, opts = {}) {
|
|
|
9076
10958
|
done = true;
|
|
9077
10959
|
clearTimeout(timer);
|
|
9078
10960
|
if (!v) cleanup(server);
|
|
9079
|
-
|
|
10961
|
+
resolve7(v);
|
|
9080
10962
|
};
|
|
9081
10963
|
const timer = setTimeout(() => finish(null), opts.waitMs ?? FIRST_LINE_WAIT_MS2);
|
|
9082
10964
|
void startChannelServer(
|
|
@@ -9139,10 +11021,10 @@ function childEnv(src, windowId) {
|
|
|
9139
11021
|
}
|
|
9140
11022
|
function wrapLog(line) {
|
|
9141
11023
|
try {
|
|
9142
|
-
const p =
|
|
11024
|
+
const p = join36(deskbuddyHome(), "wrap.log");
|
|
9143
11025
|
mkdirSync26(deskbuddyHome(), { recursive: true });
|
|
9144
11026
|
try {
|
|
9145
|
-
if (
|
|
11027
|
+
if (statSync13(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
|
|
9146
11028
|
} catch {
|
|
9147
11029
|
}
|
|
9148
11030
|
appendFileSync2(p, `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${line}
|
|
@@ -9459,7 +11341,7 @@ function runAttached(tool, p, term0, windowId, deps, launched = false) {
|
|
|
9459
11341
|
injector.tick().catch(() => {
|
|
9460
11342
|
});
|
|
9461
11343
|
}, TICK_MS);
|
|
9462
|
-
return new Promise((
|
|
11344
|
+
return new Promise((resolve7) => {
|
|
9463
11345
|
let degraded = false;
|
|
9464
11346
|
const onFatal = (e) => {
|
|
9465
11347
|
deps.log(`\u5305\u88C5\u5C42\u5F02\u5E38\uFF1A${e?.stack ?? String(e)}`);
|
|
@@ -9510,8 +11392,8 @@ ${WRAP_HINT}\uFF08\u8BBE\u5907\u56DE\u590D\u6682\u65F6\u5199\u4E0D\u8FDB\u8FD9\u
|
|
|
9510
11392
|
restoreTerminal();
|
|
9511
11393
|
process.removeListener("uncaughtException", onFatal);
|
|
9512
11394
|
for (const [s, h] of sigHandlers) process.removeListener(s, h);
|
|
9513
|
-
if (pending > 0) setTimeout(() =>
|
|
9514
|
-
else
|
|
11395
|
+
if (pending > 0) setTimeout(() => resolve7(code), 150);
|
|
11396
|
+
else resolve7(code);
|
|
9515
11397
|
}
|
|
9516
11398
|
process.on("uncaughtException", onFatal);
|
|
9517
11399
|
for (const [s, h] of sigHandlers) {
|