deskbuddy 0.2.0-beta.39 → 0.2.0-beta.40
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
CHANGED
|
@@ -5,10 +5,12 @@ import {
|
|
|
5
5
|
APP_IN_USE_REASON,
|
|
6
6
|
APP_NOT_RUNNING,
|
|
7
7
|
APP_PORT_HUNG_REASON,
|
|
8
|
+
APP_PORT_MISSING,
|
|
8
9
|
APP_SECRET,
|
|
9
10
|
APP_WAIT_MARGIN_MS,
|
|
10
11
|
ApiError,
|
|
11
12
|
CODEBUDDY_TEXT,
|
|
13
|
+
CODEX_PERMISSIONS_TOOL,
|
|
12
14
|
CODE_NOT_SUPPORT,
|
|
13
15
|
COZE_BRAND,
|
|
14
16
|
COZE_DEFAULT_CDP_PORT,
|
|
@@ -35,8 +37,12 @@ import {
|
|
|
35
37
|
QODER_APP_POLL_MS,
|
|
36
38
|
QODER_BRANDS,
|
|
37
39
|
QODER_SHORTCUT_EVERY_MS,
|
|
40
|
+
QW_DEFAULT_CDP_PORT,
|
|
41
|
+
QW_SHORTCUT_EVERY_MS,
|
|
38
42
|
QoderAppTasks,
|
|
39
43
|
QoderCdpKeeper,
|
|
44
|
+
QwShortcutRelaunchFixer,
|
|
45
|
+
QwenworkKeeper,
|
|
40
46
|
RUN_ID_RE,
|
|
41
47
|
SESSION_UUID_RE,
|
|
42
48
|
SHIM_COMMANDS,
|
|
@@ -85,6 +91,7 @@ import {
|
|
|
85
91
|
codexFileOptions,
|
|
86
92
|
codexHome,
|
|
87
93
|
codexMcpOptions,
|
|
94
|
+
codexPermissionsCard,
|
|
88
95
|
compareVersions,
|
|
89
96
|
connectCdp,
|
|
90
97
|
daemonCheckDeps,
|
|
@@ -109,6 +116,7 @@ import {
|
|
|
109
116
|
ensureClientId,
|
|
110
117
|
ensureDumateShortcuts,
|
|
111
118
|
ensureQoderShortcuts,
|
|
119
|
+
ensureQwenworkShortcuts,
|
|
112
120
|
envOverride,
|
|
113
121
|
environmentLabel,
|
|
114
122
|
failureHead,
|
|
@@ -194,6 +202,7 @@ import {
|
|
|
194
202
|
qwenworkDataHome,
|
|
195
203
|
qwenworkHome,
|
|
196
204
|
qwenworkOptions,
|
|
205
|
+
qwenworkShortcutsWanted,
|
|
197
206
|
readAliveStates,
|
|
198
207
|
readEnded,
|
|
199
208
|
readFailed,
|
|
@@ -262,7 +271,7 @@ import {
|
|
|
262
271
|
writeState,
|
|
263
272
|
writeWakeMark,
|
|
264
273
|
writeWakeSecret
|
|
265
|
-
} from "./chunk-
|
|
274
|
+
} from "./chunk-YZGPNQYG.js";
|
|
266
275
|
|
|
267
276
|
// src/commands/config.ts
|
|
268
277
|
function runConfig(args, out = (s2) => process.stdout.write(s2 + "\n")) {
|
|
@@ -795,7 +804,7 @@ var AutoEnabler = class {
|
|
|
795
804
|
|
|
796
805
|
// src/daemon/index.ts
|
|
797
806
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
798
|
-
import { existsSync as existsSync7, readdirSync as
|
|
807
|
+
import { existsSync as existsSync7, readdirSync as readdirSync12, readFileSync as readFileSync14, statSync as statSync13 } from "fs";
|
|
799
808
|
import { homedir as homedir8 } from "os";
|
|
800
809
|
import { basename as basename9, dirname as dirname9, join as join28 } from "path";
|
|
801
810
|
import WebSocket2 from "ws";
|
|
@@ -2425,7 +2434,8 @@ function describe(toolName, toolInput, cwd) {
|
|
|
2425
2434
|
if (v.action === "ask") return { kind: v.kind, summary: v.summary };
|
|
2426
2435
|
const input = toolInput && typeof toolInput === "object" ? toolInput : {};
|
|
2427
2436
|
const detail = str(input.command) || str(input.file_path) || str(input.path) || str(input.pattern) || str(input.url);
|
|
2428
|
-
|
|
2437
|
+
if (SHELL_TOOLS.has(toolName)) return { kind: "command", summary: truncate(detail || toolName) };
|
|
2438
|
+
return { kind: "other", summary: truncate(detail ? `${toolName} ${detail}` : toolName) };
|
|
2429
2439
|
}
|
|
2430
2440
|
function editInProject(toolName, toolInput, cwd, opts) {
|
|
2431
2441
|
if (!opts.real || !EDIT_TOOLS.has(toolName) && !PATCH_TOOLS.has(toolName)) return false;
|
|
@@ -3707,7 +3717,8 @@ var ApprovalCoordinator = class {
|
|
|
3707
3717
|
const trust = toolName === TRUST_TOOL_NAME;
|
|
3708
3718
|
if (trust !== sessionId.startsWith(TRUST_SESSION_PREFIX)) return none;
|
|
3709
3719
|
const { kind, summary: summary2 } = qv ? { kind: "question", summary: qv.summary } : trust ? { kind: "other", summary: trustCardSummary(cwd, hook.tool_input) } : describe(toolName, hook.tool_input, cwd ? cwdForms(cwd) : []);
|
|
3710
|
-
const
|
|
3720
|
+
const codexPerm = !viaGate && tool === "codex" && toolName === CODEX_PERMISSIONS_TOOL;
|
|
3721
|
+
const ownSummary = !viaGate && (tool === "workbuddy" || tool === "coze" || codexPerm || isQwAppCard(tool, hook, viaGate) || isQoderAppCard(tool, hook, viaGate)) && !qv && typeof hook.deskbuddy_summary === "string" ? truncate(hook.deskbuddy_summary) : "";
|
|
3711
3722
|
const traeSummary = tool === "trae" ? truncate(traeNoticeSummary(toolName, hook.tool_input, summary2, hook.trae_notice)) : "";
|
|
3712
3723
|
const cardSummary = ownSummary || traeSummary || summary2;
|
|
3713
3724
|
const inProject = kind === "edit" && !qv && !trust && !!cwd && editInProject(toolName, hook.tool_input, cwdForms(cwd), { real: realpathNearest });
|
|
@@ -5711,7 +5722,7 @@ function sleep(ms) {
|
|
|
5711
5722
|
|
|
5712
5723
|
// src/daemon/qwenworkApp/watch.ts
|
|
5713
5724
|
import { homedir as homedir4 } from "os";
|
|
5714
|
-
import { join as
|
|
5725
|
+
import { join as join14 } from "path";
|
|
5715
5726
|
|
|
5716
5727
|
// src/daemon/appReasons.ts
|
|
5717
5728
|
var REASON_APP_EXITED = "\u7535\u8111\u4E0A\u7684 App \u5DF2\u9000\u51FA\uFF0C\u8BF7\u5728\u7535\u8111\u4E0A\u5904\u7406";
|
|
@@ -6032,79 +6043,6 @@ function answerOf(set, native, decision) {
|
|
|
6032
6043
|
return null;
|
|
6033
6044
|
}
|
|
6034
6045
|
|
|
6035
|
-
// src/daemon/qwenworkApp/keeper.ts
|
|
6036
|
-
import { join as join14 } from "path";
|
|
6037
|
-
var QW_DEFAULT_CDP_PORT = 39237;
|
|
6038
|
-
var QWENWORK_BRAND = {
|
|
6039
|
-
tool: "qwenwork",
|
|
6040
|
-
label: "\u5343\u95EE\u529E\u516C",
|
|
6041
|
-
macApp: "/Applications/QwenWorkCN.app",
|
|
6042
|
-
macExe: "/Applications/QwenWorkCN.app/Contents/MacOS/QwenWorkCN",
|
|
6043
|
-
appName: "QwenWorkCN",
|
|
6044
|
-
bundleId: "cn.qwenwork.desktop.mac",
|
|
6045
|
-
// Electron 默认 UA 带 `<应用名>/<版本>`;包里 package.json 的 name 是 qwen-work-cn、bundle 名是 QwenWorkCN(待真机核对是哪一个)
|
|
6046
|
-
ua: /\b(QwenWorkCN|qwen-work-cn)\/\d/i,
|
|
6047
|
-
comm: /\/QwenWorkCN\.app\//,
|
|
6048
|
-
defaultPort: QW_DEFAULT_CDP_PORT
|
|
6049
|
-
};
|
|
6050
|
-
var QW_DOWN_ROUNDS = 2;
|
|
6051
|
-
function qwKeeperThrottleFile() {
|
|
6052
|
-
return join14(deskbuddyHome(), "qwapp", "keeper.json");
|
|
6053
|
-
}
|
|
6054
|
-
var QwenworkKeeper = class {
|
|
6055
|
-
#d;
|
|
6056
|
-
#inner;
|
|
6057
|
-
#down = 0;
|
|
6058
|
-
#lastResult = "";
|
|
6059
|
-
constructor(deps2) {
|
|
6060
|
-
this.#d = deps2;
|
|
6061
|
-
this.#inner = deps2.inner ?? new QoderCdpKeeper({
|
|
6062
|
-
...deps2,
|
|
6063
|
-
brand: QWENWORK_BRAND,
|
|
6064
|
-
throttleFile: deps2.throttleFile ?? qwKeeperThrottleFile,
|
|
6065
|
-
skipSystem: deps2.skipSystem ?? (() => process.env.DESKBUDDY_SKIP_QW_CDP === "1"),
|
|
6066
|
-
busy: () => (deps2.busy ? deps2.busy() : toolBusy("qwenwork")) || deps2.hasCards?.() === true
|
|
6067
|
-
});
|
|
6068
|
-
}
|
|
6069
|
-
#platform() {
|
|
6070
|
-
return this.#d.platform ?? process.platform;
|
|
6071
|
-
}
|
|
6072
|
-
async tick() {
|
|
6073
|
-
if (this.#platform() !== "darwin") return "untrusted";
|
|
6074
|
-
if (!this.#d.inner && (this.#d.skipSystem ? this.#d.skipSystem() : process.env.DESKBUDDY_SKIP_QW_CDP === "1")) return "down";
|
|
6075
|
-
const st = await this.#inner.tick();
|
|
6076
|
-
if (st !== "down") {
|
|
6077
|
-
this.#down = 0;
|
|
6078
|
-
return st;
|
|
6079
|
-
}
|
|
6080
|
-
this.#down++;
|
|
6081
|
-
if (this.#down >= QW_DOWN_ROUNDS) await this.#maybeRestart().catch((e) => this.#d.log(`\u5343\u95EE\u529E\u516C\u8C03\u8BD5\u7AEF\u53E3\uFF1A\u91CD\u542F\u51FA\u9519 ${e?.message ?? e}`));
|
|
6082
|
-
return "down";
|
|
6083
|
-
}
|
|
6084
|
-
async #maybeRestart() {
|
|
6085
|
-
if (this.#d.hasCards?.() === true) return;
|
|
6086
|
-
if (!await this.#inner.appRunning()) {
|
|
6087
|
-
this.#lastResult = "";
|
|
6088
|
-
return;
|
|
6089
|
-
}
|
|
6090
|
-
const r = await this.#inner.wakeForTask();
|
|
6091
|
-
if (r === "restarted") this.#down = 0;
|
|
6092
|
-
if (r !== this.#lastResult) {
|
|
6093
|
-
this.#lastResult = r;
|
|
6094
|
-
const why = {
|
|
6095
|
-
hung: "\u7AEF\u53E3\u5728\u76D1\u542C\u4F46\u65E0\u54CD\u5E94\uFF08App \u53EF\u80FD\u5361\u4F4F\u4E86\uFF09\uFF0C\u6309\u5361\u6B7B\u7B56\u7565\u5904\u7406\uFF1A\u8FDE\u7EED\u6EE1 20 \u5206\u949F\u3001\u7528\u6237\u4E0D\u5728\u573A\u4E14\u7A7A\u95F2\u624D\u7ED3\u675F\u5E76\u5E26\u7AEF\u53E3\u91CD\u542F\uFF0C\u5426\u5219\u8BF7\u5728\u7535\u8111\u4E0A\u91CD\u5F00\u5343\u95EE\u529E\u516C",
|
|
6096
|
-
restarted: "\u5DF2\u9000\u51FA\u5E76\u5E26\u7AEF\u53E3\u91CD\u542F",
|
|
6097
|
-
busy: "\u6B63\u5728\u8DD1\u4EFB\u52A1\uFF0C\u7B49\u7A7A\u95F2\u518D\u91CD\u542F",
|
|
6098
|
-
present: "\u7528\u6237\u5728\u7535\u8111\u524D\uFF0C\u4E0D\u6253\u65AD",
|
|
6099
|
-
failed: "\u91CD\u542F\u5931\u8D25\u6216\u4ECA\u5929\u7684\u91CD\u542F\u6B21\u6570\u5DF2\u7528\u5B8C",
|
|
6100
|
-
launched: "\u521A\u62C9\u8D77\u8FC7\uFF0C\u7B49\u5B83\u8D77\u6765",
|
|
6101
|
-
not_installed: "\u6CA1\u627E\u5230 App \u672C\u4F53"
|
|
6102
|
-
};
|
|
6103
|
-
if (why[r]) this.#d.log(`\u5343\u95EE\u529E\u516C\u8C03\u8BD5\u7AEF\u53E3 ${this.#d.port()} \u4E0D\u5728\uFF1A${why[r]}`);
|
|
6104
|
-
}
|
|
6105
|
-
}
|
|
6106
|
-
};
|
|
6107
|
-
|
|
6108
6046
|
// src/daemon/qwenworkApp/page.ts
|
|
6109
6047
|
var PAGE_VER = 1;
|
|
6110
6048
|
var STATE_KEY = "__deskbuddyQwApp";
|
|
@@ -6291,6 +6229,18 @@ var QwenworkAppWatch = class {
|
|
|
6291
6229
|
return false;
|
|
6292
6230
|
}
|
|
6293
6231
|
}
|
|
6232
|
+
/**
|
|
6233
|
+
* 千问办公开着、但调试端口没连上(没带端口 / 监听者不对,Bug 表 274):设备新建失败时据此回 app_port_missing 而不是 app_not_running。
|
|
6234
|
+
* 端口状态还没巡检过 / 这一路没启用 → 判不准,按否
|
|
6235
|
+
*/
|
|
6236
|
+
async portMissing() {
|
|
6237
|
+
try {
|
|
6238
|
+
if (this.#stopped || !this.#d.enabled() || this.#keeperAt === 0 || this.#portStatus === "up") return false;
|
|
6239
|
+
return await this.#keeper.appRunning?.() === true;
|
|
6240
|
+
} catch {
|
|
6241
|
+
return false;
|
|
6242
|
+
}
|
|
6243
|
+
}
|
|
6294
6244
|
/** hook 那一路的 preferApp:App 画的卡(页面发布等)与 App 自己的工具,连着时让给这里 */
|
|
6295
6245
|
prefers(toolName) {
|
|
6296
6246
|
return this.available() && (APP_CONFIRM_TOOL_RE.test(toolName) || APP_ONLY_TOOLS.has(toolName));
|
|
@@ -6518,7 +6468,7 @@ var QwenworkAppWatch = class {
|
|
|
6518
6468
|
if (cached) return cached;
|
|
6519
6469
|
let sid = null;
|
|
6520
6470
|
try {
|
|
6521
|
-
const root = this.#d.workspaceRoot ??
|
|
6471
|
+
const root = this.#d.workspaceRoot ?? join14(homedir4(), ".qwenworkcn", "workspace");
|
|
6522
6472
|
sid = (this.#d.findSession ?? ((id) => findQwenworkSession(id, root)))(p.chatId);
|
|
6523
6473
|
} catch {
|
|
6524
6474
|
sid = null;
|
|
@@ -6531,14 +6481,14 @@ var QwenworkAppWatch = class {
|
|
|
6531
6481
|
const card = { id: p.id, set, notice: c.type === "notice", ...c.type === "plain" ? { plain: { allow: c.allow, deny: c.deny } } : {}, ac: new AbortController() };
|
|
6532
6482
|
const { toolName, toolInput, summary: summary2 } = c;
|
|
6533
6483
|
this.#cards.set(p.id, card);
|
|
6534
|
-
const root = this.#d.workspaceRoot ??
|
|
6484
|
+
const root = this.#d.workspaceRoot ?? join14(homedir4(), ".qwenworkcn", "workspace");
|
|
6535
6485
|
const req = {
|
|
6536
6486
|
window: true,
|
|
6537
6487
|
tool: "qwenwork",
|
|
6538
6488
|
hook: {
|
|
6539
6489
|
hook_event_name: "PermissionRequest",
|
|
6540
6490
|
session_id: this.#sessionOf(p),
|
|
6541
|
-
cwd: p.chatId ?
|
|
6491
|
+
cwd: p.chatId ? join14(root, p.chatId) : "",
|
|
6542
6492
|
tool_name: toolName,
|
|
6543
6493
|
tool_input: toolInput,
|
|
6544
6494
|
...set ? { deskbuddy_options: set } : {},
|
|
@@ -6586,7 +6536,7 @@ var QwenworkAppWatch = class {
|
|
|
6586
6536
|
|
|
6587
6537
|
// src/daemon/cozeApp/watch.ts
|
|
6588
6538
|
import { homedir as homedir6 } from "os";
|
|
6589
|
-
import { join as
|
|
6539
|
+
import { join as join16 } from "path";
|
|
6590
6540
|
|
|
6591
6541
|
// src/daemon/cozeApp/cards.ts
|
|
6592
6542
|
var NOTICE_BUTTON = "\u53BB\u7535\u8111\u4E0A\u5904\u7406";
|
|
@@ -6712,13 +6662,13 @@ function questionBody(map, decision, answerLists) {
|
|
|
6712
6662
|
// src/daemon/cozeApp/keeper.ts
|
|
6713
6663
|
import { lstatSync as lstatSync2, readdirSync as readdirSync6 } from "fs";
|
|
6714
6664
|
import { homedir as homedir5 } from "os";
|
|
6715
|
-
import { join as
|
|
6665
|
+
import { join as join15 } from "path";
|
|
6716
6666
|
var COZE_DOWN_ROUNDS = 2;
|
|
6717
6667
|
var COZE_ACTIVE_MS = 10 * 60 * 1e3;
|
|
6718
6668
|
var WALK_MAX_ENTRIES = 3e3;
|
|
6719
6669
|
var WALK_MAX_DEPTH = 3;
|
|
6720
6670
|
function cozeUserDataDir(home = homedir5()) {
|
|
6721
|
-
return
|
|
6671
|
+
return join15(home, "Library", "Application Support", "Coze");
|
|
6722
6672
|
}
|
|
6723
6673
|
function cozeActivity(dir2 = cozeUserDataDir(), now = Date.now()) {
|
|
6724
6674
|
let seen = 0;
|
|
@@ -6733,7 +6683,7 @@ function cozeActivity(dir2 = cozeUserDataDir(), now = Date.now()) {
|
|
|
6733
6683
|
if (depth === 0) readable = true;
|
|
6734
6684
|
for (const n of names) {
|
|
6735
6685
|
if (++seen > WALK_MAX_ENTRIES) return false;
|
|
6736
|
-
const p =
|
|
6686
|
+
const p = join15(d, n);
|
|
6737
6687
|
let st;
|
|
6738
6688
|
try {
|
|
6739
6689
|
st = lstatSync2(p);
|
|
@@ -6759,7 +6709,7 @@ function cozeKeeperBusy(d) {
|
|
|
6759
6709
|
}
|
|
6760
6710
|
}
|
|
6761
6711
|
function cozeKeeperThrottleFile() {
|
|
6762
|
-
return
|
|
6712
|
+
return join15(deskbuddyHome(), "cozeapp", "keeper.json");
|
|
6763
6713
|
}
|
|
6764
6714
|
var CozeKeeper = class {
|
|
6765
6715
|
#d;
|
|
@@ -7435,7 +7385,7 @@ async function defaultMainPid(exec, exe = COZE_BRAND.macExe) {
|
|
|
7435
7385
|
if (process.platform !== "darwin" || process.env.DESKBUDDY_SKIP_COZE_CDP === "1") return 0;
|
|
7436
7386
|
const r = await exec("ps", ["-axo", "pid=,args="]);
|
|
7437
7387
|
if (r.code !== 0) return 0;
|
|
7438
|
-
const home =
|
|
7388
|
+
const home = join16(homedir6(), "Applications", "\u6263\u5B50.app", "Contents", "MacOS", "Coze");
|
|
7439
7389
|
for (const line of r.stdout.split(/\r?\n/)) {
|
|
7440
7390
|
const m = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
7441
7391
|
if (m && (isMainArgs(m[2], exe) || isMainArgs(m[2], home))) return Number(m[1]);
|
|
@@ -8839,7 +8789,7 @@ var QoderAppConn = class {
|
|
|
8839
8789
|
|
|
8840
8790
|
// src/daemon/qoderApp/hookQuestions.ts
|
|
8841
8791
|
import { closeSync as closeSync5, existsSync as existsSync3, openSync as openSync5, readdirSync as readdirSync7, readFileSync as readFileSync8, readSync as readSync4, statSync as statSync6 } from "fs";
|
|
8842
|
-
import { join as
|
|
8792
|
+
import { join as join17 } from "path";
|
|
8843
8793
|
var HOOK_QUESTION_MAX_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
8844
8794
|
var TAIL_BYTES2 = 256 * 1024;
|
|
8845
8795
|
var BUSY = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
|
|
@@ -8911,7 +8861,7 @@ function scanHookQuestions(tool, now) {
|
|
|
8911
8861
|
if (!f.endsWith(".json")) continue;
|
|
8912
8862
|
let s2;
|
|
8913
8863
|
try {
|
|
8914
|
-
s2 = JSON.parse(readFileSync8(
|
|
8864
|
+
s2 = JSON.parse(readFileSync8(join17(dir2, f), "utf8"));
|
|
8915
8865
|
} catch {
|
|
8916
8866
|
continue;
|
|
8917
8867
|
}
|
|
@@ -10307,7 +10257,7 @@ import { dirname as dirname4, sep as sep3 } from "path";
|
|
|
10307
10257
|
// src/core/wrapWindows.ts
|
|
10308
10258
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync9, readdirSync as readdirSync8, readFileSync as readFileSync9, renameSync as renameSync6, rmSync as rmSync5, statSync as statSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
10309
10259
|
import { tmpdir } from "os";
|
|
10310
|
-
import { join as
|
|
10260
|
+
import { join as join18 } from "path";
|
|
10311
10261
|
var WINDOW_ID_RE = /^[a-f0-9]{32}$/;
|
|
10312
10262
|
var WINDOW_EVENTS = [
|
|
10313
10263
|
"SessionStart",
|
|
@@ -10320,10 +10270,10 @@ var WINDOW_EVENTS = [
|
|
|
10320
10270
|
"SessionEnd"
|
|
10321
10271
|
];
|
|
10322
10272
|
function windowsDir() {
|
|
10323
|
-
return
|
|
10273
|
+
return join18(deskbuddyHome(), "windows");
|
|
10324
10274
|
}
|
|
10325
10275
|
function bySessionDir() {
|
|
10326
|
-
return
|
|
10276
|
+
return join18(windowsDir(), "by-session");
|
|
10327
10277
|
}
|
|
10328
10278
|
function safeSid(sessionId) {
|
|
10329
10279
|
return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
|
|
@@ -10337,7 +10287,7 @@ function atomicWrite(file, text) {
|
|
|
10337
10287
|
renameSync6(tmp, file);
|
|
10338
10288
|
}
|
|
10339
10289
|
function windowFile(windowId) {
|
|
10340
|
-
return
|
|
10290
|
+
return join18(windowsDir(), `${windowId}.json`);
|
|
10341
10291
|
}
|
|
10342
10292
|
function writeWindow(windowId, rec) {
|
|
10343
10293
|
if (!WINDOW_ID_RE.test(windowId)) return;
|
|
@@ -10373,7 +10323,7 @@ function removeWindow(windowId) {
|
|
|
10373
10323
|
return;
|
|
10374
10324
|
}
|
|
10375
10325
|
for (const n of names) {
|
|
10376
|
-
const f =
|
|
10326
|
+
const f = join18(bySessionDir(), n);
|
|
10377
10327
|
try {
|
|
10378
10328
|
if (readFileSync9(f, "utf8").trim() === windowId) rmSync5(f, { force: true });
|
|
10379
10329
|
} catch {
|
|
@@ -10384,14 +10334,14 @@ function readBySession(sessionId) {
|
|
|
10384
10334
|
const safe2 = safeSid(sessionId);
|
|
10385
10335
|
if (!safe2) return null;
|
|
10386
10336
|
try {
|
|
10387
|
-
const id = readFileSync9(
|
|
10337
|
+
const id = readFileSync9(join18(bySessionDir(), safe2), "utf8").trim();
|
|
10388
10338
|
return WINDOW_ID_RE.test(id) ? id : null;
|
|
10389
10339
|
} catch {
|
|
10390
10340
|
return null;
|
|
10391
10341
|
}
|
|
10392
10342
|
}
|
|
10393
10343
|
function lastEventFile(windowId) {
|
|
10394
|
-
return
|
|
10344
|
+
return join18(windowsDir(), `${windowId}.last`);
|
|
10395
10345
|
}
|
|
10396
10346
|
function readLastEvent(windowId) {
|
|
10397
10347
|
if (!WINDOW_ID_RE.test(windowId)) return null;
|
|
@@ -10410,13 +10360,13 @@ function writeLastEvent(windowId, ev, sid, at) {
|
|
|
10410
10360
|
function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
|
|
10411
10361
|
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
|
|
10412
10362
|
const name = `${windowId.slice(0, 16)}.sock`;
|
|
10413
|
-
const primary =
|
|
10363
|
+
const primary = join18(home, "win", name);
|
|
10414
10364
|
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
10415
10365
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
10416
|
-
return
|
|
10366
|
+
return join18(tmpdir(), `deskbuddy-${uid}`, name);
|
|
10417
10367
|
}
|
|
10418
10368
|
function ensureChannelDir(sockPath) {
|
|
10419
|
-
const dir2 =
|
|
10369
|
+
const dir2 = join18(sockPath, "..");
|
|
10420
10370
|
try {
|
|
10421
10371
|
mkdirSync9(dir2, { recursive: true, mode: 448 });
|
|
10422
10372
|
const st = statSync7(dir2);
|
|
@@ -11517,7 +11467,7 @@ var CodebuddyTerminal = class {
|
|
|
11517
11467
|
// src/wrap/trustPrompt.ts
|
|
11518
11468
|
import { existsSync as existsSync4, realpathSync as realpathSync4, statSync as statSync10 } from "fs";
|
|
11519
11469
|
import { homedir as homedir7 } from "os";
|
|
11520
|
-
import { dirname as dirname5, join as
|
|
11470
|
+
import { dirname as dirname5, join as join19, resolve as resolve5 } from "path";
|
|
11521
11471
|
var TRUST_TOOLS = /* @__PURE__ */ new Set(["claude_code", "workbuddy", "codex"]);
|
|
11522
11472
|
var CURSOR_CHARS = "\u276F>\u203A";
|
|
11523
11473
|
var BOX = /[─-╿|]/g;
|
|
@@ -11725,7 +11675,7 @@ function scanRepo(start, stat2, home) {
|
|
|
11725
11675
|
let d = start.replace(/[\\/]+$/, "") || start;
|
|
11726
11676
|
let aboveHome = false;
|
|
11727
11677
|
for (let i = 0; i < 64; i++) {
|
|
11728
|
-
const g = stat2(
|
|
11678
|
+
const g = stat2(join19(d, ".git"));
|
|
11729
11679
|
if (g) {
|
|
11730
11680
|
if (aboveHome) return { kind: "unknown" };
|
|
11731
11681
|
if (g === "file") return { kind: "worktree", at: d };
|
|
@@ -12770,6 +12720,44 @@ var DumateCdp = class {
|
|
|
12770
12720
|
// src/daemon/codexWatch.ts
|
|
12771
12721
|
import { execFileSync as execFileSync3, spawn as spawn2 } from "child_process";
|
|
12772
12722
|
import WebSocket from "ws";
|
|
12723
|
+
var CodexClaims = class {
|
|
12724
|
+
#m = /* @__PURE__ */ new Map();
|
|
12725
|
+
#wait = /* @__PURE__ */ new Map();
|
|
12726
|
+
claim(key, owner) {
|
|
12727
|
+
const cur = this.#m.get(key);
|
|
12728
|
+
if (cur && cur !== owner) return false;
|
|
12729
|
+
this.#m.set(key, owner);
|
|
12730
|
+
return true;
|
|
12731
|
+
}
|
|
12732
|
+
wait(key, owner, fire) {
|
|
12733
|
+
const q = this.#wait.get(key) ?? [];
|
|
12734
|
+
if (!q.some((w) => w.owner === owner)) q.push({ owner, fire });
|
|
12735
|
+
this.#wait.set(key, q);
|
|
12736
|
+
}
|
|
12737
|
+
unwait(key, owner) {
|
|
12738
|
+
const q = (this.#wait.get(key) ?? []).filter((w) => w.owner !== owner);
|
|
12739
|
+
if (q.length) this.#wait.set(key, q);
|
|
12740
|
+
else this.#wait.delete(key);
|
|
12741
|
+
}
|
|
12742
|
+
/** withdrawn = 占位方是撤卡(不是答复 / 已了结):交给下一个备用方补推 */
|
|
12743
|
+
release(key, owner, withdrawn = false) {
|
|
12744
|
+
if (this.#m.get(key) !== owner) return;
|
|
12745
|
+
this.#m.delete(key);
|
|
12746
|
+
if (!withdrawn) return;
|
|
12747
|
+
const q = this.#wait.get(key);
|
|
12748
|
+
const next = q?.shift();
|
|
12749
|
+
if (q && !q.length) this.#wait.delete(key);
|
|
12750
|
+
next?.fire();
|
|
12751
|
+
}
|
|
12752
|
+
get size() {
|
|
12753
|
+
return this.#m.size;
|
|
12754
|
+
}
|
|
12755
|
+
get waiting() {
|
|
12756
|
+
let n = 0;
|
|
12757
|
+
for (const q of this.#wait.values()) n += q.length;
|
|
12758
|
+
return n;
|
|
12759
|
+
}
|
|
12760
|
+
};
|
|
12773
12761
|
var TURN_LIMIT_MS = 10 * 60 * 1e3;
|
|
12774
12762
|
var QUEUE_MAX_MS2 = 10 * 60 * 1e3;
|
|
12775
12763
|
var IDLE_GRACE_MS = 5 * 1e3;
|
|
@@ -12917,8 +12905,171 @@ var CodexWatch = class {
|
|
|
12917
12905
|
#early = /* @__PURE__ */ new Map();
|
|
12918
12906
|
/** 会话 → 等状态变化的回调(排队等空闲用) */
|
|
12919
12907
|
#wake = /* @__PURE__ */ new Map();
|
|
12908
|
+
/** ws 主连接的请求处理上下文 */
|
|
12909
|
+
#main;
|
|
12910
|
+
/** 额外通道(名字 → 上下文) */
|
|
12911
|
+
#channels = /* @__PURE__ */ new Map();
|
|
12912
|
+
#claims;
|
|
12913
|
+
/** 主连接上去重抢不到的备用请求 */
|
|
12914
|
+
#standby = /* @__PURE__ */ new Map();
|
|
12920
12915
|
constructor(deps2) {
|
|
12921
12916
|
this.#d = { connect: (url) => new WebSocket(url), ...deps2 };
|
|
12917
|
+
this.#claims = deps2.claims ?? new CodexClaims();
|
|
12918
|
+
this.#main = {
|
|
12919
|
+
name: "",
|
|
12920
|
+
label: "Codex \u7A97\u53E3",
|
|
12921
|
+
cwd: this.#cwd,
|
|
12922
|
+
changes: this.#changes,
|
|
12923
|
+
mcpCalls: this.#mcpCalls,
|
|
12924
|
+
status: this.#status,
|
|
12925
|
+
pending: this.#pending,
|
|
12926
|
+
standby: this.#standby,
|
|
12927
|
+
keyOf: (_threadId, id) => String(id),
|
|
12928
|
+
reply: (_threadId, id, result) => this.#send({ id, result }),
|
|
12929
|
+
settled: (threadId) => {
|
|
12930
|
+
if (this.#idle.has(threadId)) this.#unsubscribe(threadId);
|
|
12931
|
+
},
|
|
12932
|
+
feedback: (threadId, text) => this.#sendFeedback(threadId, text),
|
|
12933
|
+
inflight: (threadId) => this.#inflight.get(threadId)
|
|
12934
|
+
};
|
|
12935
|
+
}
|
|
12936
|
+
/**
|
|
12937
|
+
* 挂一条额外通道(同名的先关掉旧的)。请求 key = `<name>:<threadId>:<id>`,答复回到这条通道;
|
|
12938
|
+
* 会话状态 / 订阅 / 发一轮都不管(只管待确认),不报会话状态
|
|
12939
|
+
*/
|
|
12940
|
+
attachChannel(name, opts) {
|
|
12941
|
+
if (this.#channels.has(name)) this.#closeChannel(name);
|
|
12942
|
+
const ctx = {
|
|
12943
|
+
name,
|
|
12944
|
+
label: opts.label,
|
|
12945
|
+
cwd: /* @__PURE__ */ new Map(),
|
|
12946
|
+
changes: /* @__PURE__ */ new Map(),
|
|
12947
|
+
mcpCalls: /* @__PURE__ */ new Map(),
|
|
12948
|
+
status: /* @__PURE__ */ new Map(),
|
|
12949
|
+
pending: /* @__PURE__ */ new Map(),
|
|
12950
|
+
standby: /* @__PURE__ */ new Map(),
|
|
12951
|
+
keyOf: (threadId, id) => `${name}:${threadId}:${String(id)}`,
|
|
12952
|
+
reply: (threadId, id, result) => opts.respond(threadId, id, result),
|
|
12953
|
+
settled: () => void 0,
|
|
12954
|
+
// App 里没法替用户发下一轮:附带的说明不写进会话(只按拒绝回),日志不记内容
|
|
12955
|
+
feedback: (threadId) => this.#d.log(`${opts.label} \u4F1A\u8BDD ${threadId.slice(0, 8)}\uFF1A\u8BBE\u5907\u62D2\u7EDD\u65F6\u9644\u4E86\u8BF4\u660E\uFF0C\u8FD9\u6761\u901A\u9053\u5199\u4E0D\u8FDB\u4F1A\u8BDD\uFF0C\u53EA\u6309\u62D2\u7EDD\u56DE`),
|
|
12956
|
+
inflight: () => void 0
|
|
12957
|
+
};
|
|
12958
|
+
this.#channels.set(name, ctx);
|
|
12959
|
+
return {
|
|
12960
|
+
onMessage: (msg) => {
|
|
12961
|
+
if (this.#channels.get(name) !== ctx) return;
|
|
12962
|
+
this.#onChannelMessage(ctx, msg);
|
|
12963
|
+
},
|
|
12964
|
+
pending: () => this.#channels.get(name) === ctx ? ctx.pending.size : 0,
|
|
12965
|
+
close: () => {
|
|
12966
|
+
if (this.#channels.get(name) === ctx) this.#closeChannel(name);
|
|
12967
|
+
}
|
|
12968
|
+
};
|
|
12969
|
+
}
|
|
12970
|
+
#closeChannel(name) {
|
|
12971
|
+
const ctx = this.#channels.get(name);
|
|
12972
|
+
if (!ctx) return;
|
|
12973
|
+
this.#channels.delete(name);
|
|
12974
|
+
this.#dropStandby(ctx);
|
|
12975
|
+
const gone = [...ctx.pending.values()];
|
|
12976
|
+
ctx.pending.clear();
|
|
12977
|
+
for (const e of gone) {
|
|
12978
|
+
e.ac.abort();
|
|
12979
|
+
this.#release(e, true);
|
|
12980
|
+
}
|
|
12981
|
+
ctx.cwd.clear();
|
|
12982
|
+
ctx.changes.clear();
|
|
12983
|
+
ctx.mcpCalls.clear();
|
|
12984
|
+
ctx.status.clear();
|
|
12985
|
+
}
|
|
12986
|
+
#onChannelMessage(ctx, m) {
|
|
12987
|
+
const method = typeof m.method === "string" ? m.method : "";
|
|
12988
|
+
if (!method) return;
|
|
12989
|
+
const params = m.params && typeof m.params === "object" ? m.params : {};
|
|
12990
|
+
const id = m.id;
|
|
12991
|
+
if (id !== void 0 && id !== null && (typeof id === "number" || typeof id === "string")) {
|
|
12992
|
+
this.#onServerRequest(id, method, params, ctx);
|
|
12993
|
+
return;
|
|
12994
|
+
}
|
|
12995
|
+
const tid = typeof params.threadId === "string" ? params.threadId : "";
|
|
12996
|
+
switch (method) {
|
|
12997
|
+
case "thread/started": {
|
|
12998
|
+
const t = params.thread;
|
|
12999
|
+
if (t && typeof t.id === "string" && typeof t.cwd === "string") ctx.cwd.set(t.id, t.cwd);
|
|
13000
|
+
return;
|
|
13001
|
+
}
|
|
13002
|
+
case "thread/settings/updated": {
|
|
13003
|
+
const cwd = (params.threadSettings ?? {}).cwd;
|
|
13004
|
+
if (tid && typeof cwd === "string") ctx.cwd.set(tid, cwd);
|
|
13005
|
+
return;
|
|
13006
|
+
}
|
|
13007
|
+
case "thread/status/changed": {
|
|
13008
|
+
const type2 = (params.status ?? {}).type;
|
|
13009
|
+
if (tid && typeof type2 === "string") ctx.status.set(tid, type2);
|
|
13010
|
+
return;
|
|
13011
|
+
}
|
|
13012
|
+
case "thread/closed":
|
|
13013
|
+
if (tid) {
|
|
13014
|
+
ctx.cwd.delete(tid);
|
|
13015
|
+
ctx.mcpCalls.delete(tid);
|
|
13016
|
+
ctx.status.set(tid, "notLoaded");
|
|
13017
|
+
}
|
|
13018
|
+
return;
|
|
13019
|
+
case "item/started":
|
|
13020
|
+
if (tid) this.#noteItem(ctx, tid, params.item);
|
|
13021
|
+
return;
|
|
13022
|
+
case "item/completed":
|
|
13023
|
+
this.#itemDone(ctx, params);
|
|
13024
|
+
return;
|
|
13025
|
+
case "serverRequest/resolved":
|
|
13026
|
+
this.#resolved(ctx, params);
|
|
13027
|
+
return;
|
|
13028
|
+
default:
|
|
13029
|
+
}
|
|
13030
|
+
}
|
|
13031
|
+
/** 日志前缀:主连接「Codex 窗口」,额外通道「<label> 」 */
|
|
13032
|
+
#tag(ctx) {
|
|
13033
|
+
return ctx.name ? `${ctx.label} ` : ctx.label;
|
|
13034
|
+
}
|
|
13035
|
+
/** 释放去重占位 */
|
|
13036
|
+
#release(e, withdrawn = false) {
|
|
13037
|
+
if (e.claim) this.#claims.release(e.claim, e, withdrawn);
|
|
13038
|
+
}
|
|
13039
|
+
#dropStandby(ctx) {
|
|
13040
|
+
for (const e of ctx.standby.values()) if (e.claim) this.#claims.unwait(e.claim, e);
|
|
13041
|
+
ctx.standby.clear();
|
|
13042
|
+
}
|
|
13043
|
+
/** 条目结束:清改动文件 / MCP 调用 */
|
|
13044
|
+
#itemDone(ctx, p) {
|
|
13045
|
+
const item = p.item;
|
|
13046
|
+
if (item && typeof item.id === "string") ctx.changes.delete(item.id);
|
|
13047
|
+
if (item && item.type === "mcpToolCall" && typeof p.threadId === "string") ctx.mcpCalls.delete(p.threadId);
|
|
13048
|
+
}
|
|
13049
|
+
/** 请求已了结(人在窗口 / App 里先答了,或别的客户端答了):撤设备卡片,🔴 之后绝不再回话 */
|
|
13050
|
+
#resolved(ctx, p) {
|
|
13051
|
+
const rid = String(p.requestId);
|
|
13052
|
+
const tid = typeof p.threadId === "string" ? p.threadId : "";
|
|
13053
|
+
let keys;
|
|
13054
|
+
if (!ctx.name) keys = [rid];
|
|
13055
|
+
else if (tid) keys = [ctx.keyOf(tid, rid)];
|
|
13056
|
+
else keys = [...ctx.pending.keys()].filter((k) => k.endsWith(`:${rid}`));
|
|
13057
|
+
if (ctx.name && !tid) keys = [.../* @__PURE__ */ new Set([...keys, ...[...ctx.standby.keys()].filter((k) => k.endsWith(`:${rid}`))])];
|
|
13058
|
+
for (const key of keys) {
|
|
13059
|
+
const sb = ctx.standby.get(key);
|
|
13060
|
+
if (sb) {
|
|
13061
|
+
ctx.standby.delete(key);
|
|
13062
|
+
if (sb.claim) this.#claims.unwait(sb.claim, sb);
|
|
13063
|
+
ctx.settled(sb.threadId);
|
|
13064
|
+
}
|
|
13065
|
+
const pend = ctx.pending.get(key);
|
|
13066
|
+
if (!pend) continue;
|
|
13067
|
+
ctx.pending.delete(key);
|
|
13068
|
+
this.#release(pend);
|
|
13069
|
+
pend.ac.abort();
|
|
13070
|
+
this.#reportResumed(pend, ctx);
|
|
13071
|
+
ctx.settled(pend.threadId);
|
|
13072
|
+
}
|
|
12922
13073
|
}
|
|
12923
13074
|
get connected() {
|
|
12924
13075
|
return this.#ws !== null;
|
|
@@ -12951,6 +13102,7 @@ var CodexWatch = class {
|
|
|
12951
13102
|
}
|
|
12952
13103
|
stop() {
|
|
12953
13104
|
this.#stopped = true;
|
|
13105
|
+
for (const name of [...this.#channels.keys()]) this.#closeChannel(name);
|
|
12954
13106
|
this.#teardown();
|
|
12955
13107
|
}
|
|
12956
13108
|
#open(url) {
|
|
@@ -12982,8 +13134,13 @@ var CodexWatch = class {
|
|
|
12982
13134
|
c.resolve({ error: { message: "disconnected" }, local: true });
|
|
12983
13135
|
}
|
|
12984
13136
|
this.#calls.clear();
|
|
12985
|
-
|
|
13137
|
+
this.#dropStandby(this.#main);
|
|
13138
|
+
const gone = [...this.#pending.values()];
|
|
12986
13139
|
this.#pending.clear();
|
|
13140
|
+
for (const e of gone) {
|
|
13141
|
+
e.ac.abort();
|
|
13142
|
+
this.#release(e, true);
|
|
13143
|
+
}
|
|
12987
13144
|
this.#subscribed.clear();
|
|
12988
13145
|
this.#retrying.clear();
|
|
12989
13146
|
this.#cwd.clear();
|
|
@@ -13142,16 +13299,16 @@ var CodexWatch = class {
|
|
|
13142
13299
|
}
|
|
13143
13300
|
const turns = Array.isArray(th?.turns) ? th.turns : [];
|
|
13144
13301
|
const last = turns[turns.length - 1];
|
|
13145
|
-
if (last && last.status === "inProgress" && Array.isArray(last.items)) for (const it of last.items) this.#noteItem(threadId, it);
|
|
13302
|
+
if (last && last.status === "inProgress" && Array.isArray(last.items)) for (const it of last.items) this.#noteItem(this.#main, threadId, it);
|
|
13146
13303
|
}
|
|
13147
13304
|
/** 进行中的条目:改文件记文件列表,MCP 调用记工具名 */
|
|
13148
|
-
#noteItem(threadId, item) {
|
|
13305
|
+
#noteItem(ctx, threadId, item) {
|
|
13149
13306
|
if (!item || typeof item.id !== "string") return;
|
|
13150
13307
|
if (item.type === "fileChange" && Array.isArray(item.changes)) {
|
|
13151
13308
|
const paths = item.changes.map((c) => typeof c?.path === "string" ? c.path : "").filter(Boolean);
|
|
13152
|
-
|
|
13309
|
+
ctx.changes.set(item.id, paths);
|
|
13153
13310
|
} else if (item.type === "mcpToolCall" && typeof item.server === "string" && typeof item.tool === "string") {
|
|
13154
|
-
|
|
13311
|
+
ctx.mcpCalls.set(threadId, { server: item.server, tool: item.tool, args: item.arguments });
|
|
13155
13312
|
}
|
|
13156
13313
|
}
|
|
13157
13314
|
#subscribe(threadId) {
|
|
@@ -13197,6 +13354,7 @@ var CodexWatch = class {
|
|
|
13197
13354
|
this.#retrying.delete(threadId);
|
|
13198
13355
|
if (!this.#subscribed.has(threadId)) return;
|
|
13199
13356
|
for (const p of this.#pending.values()) if (p.threadId === threadId) return;
|
|
13357
|
+
for (const p of this.#standby.values()) if (p.threadId === threadId) return;
|
|
13200
13358
|
if ((this.#own.get(threadId) ?? 0) > 0) return;
|
|
13201
13359
|
this.#subscribed.delete(threadId);
|
|
13202
13360
|
void this.#request("thread/unsubscribe", { threadId });
|
|
@@ -13409,7 +13567,7 @@ var CodexWatch = class {
|
|
|
13409
13567
|
}
|
|
13410
13568
|
const params = m.params && typeof m.params === "object" ? m.params : {};
|
|
13411
13569
|
if (id !== void 0) {
|
|
13412
|
-
this.#onServerRequest(id, method, params);
|
|
13570
|
+
this.#onServerRequest(id, method, params, this.#main);
|
|
13413
13571
|
return;
|
|
13414
13572
|
}
|
|
13415
13573
|
this.#onNotification(method, params);
|
|
@@ -13478,27 +13636,18 @@ var CodexWatch = class {
|
|
|
13478
13636
|
return;
|
|
13479
13637
|
}
|
|
13480
13638
|
case "item/started":
|
|
13481
|
-
if (typeof p.threadId === "string") this.#noteItem(p.threadId, p.item);
|
|
13639
|
+
if (typeof p.threadId === "string") this.#noteItem(this.#main, p.threadId, p.item);
|
|
13482
13640
|
return;
|
|
13483
13641
|
case "item/completed": {
|
|
13484
13642
|
const item = p.item;
|
|
13485
|
-
|
|
13486
|
-
if (item && item.type === "mcpToolCall" && typeof p.threadId === "string") this.#mcpCalls.delete(p.threadId);
|
|
13643
|
+
this.#itemDone(this.#main, p);
|
|
13487
13644
|
const own = typeof p.turnId === "string" ? this.#turns.get(p.turnId) : void 0;
|
|
13488
13645
|
if (own && item && item.type === "agentMessage") own.items.push(item);
|
|
13489
13646
|
return;
|
|
13490
13647
|
}
|
|
13491
|
-
case "serverRequest/resolved":
|
|
13492
|
-
|
|
13493
|
-
const pend = this.#pending.get(key);
|
|
13494
|
-
if (pend) {
|
|
13495
|
-
this.#pending.delete(key);
|
|
13496
|
-
pend.ac.abort();
|
|
13497
|
-
this.#reportResumed(pend);
|
|
13498
|
-
if (this.#idle.has(pend.threadId)) this.#unsubscribe(pend.threadId);
|
|
13499
|
-
}
|
|
13648
|
+
case "serverRequest/resolved":
|
|
13649
|
+
this.#resolved(this.#main, p);
|
|
13500
13650
|
return;
|
|
13501
|
-
}
|
|
13502
13651
|
default:
|
|
13503
13652
|
}
|
|
13504
13653
|
}
|
|
@@ -13506,19 +13655,19 @@ var CodexWatch = class {
|
|
|
13506
13655
|
* 我们替 Codex 报过 ask_approval 的卡片了结了(设备答了 / 窗口里答了):再报一次 watch,
|
|
13507
13656
|
* 别指望 Codex 对 MCP 工具发 PostToolUse(没有实测依据,评审)。会话已不在跑就不报,免得盖掉 Stop 的 done
|
|
13508
13657
|
*/
|
|
13509
|
-
#reportResumed(entry) {
|
|
13658
|
+
#reportResumed(entry, ctx) {
|
|
13510
13659
|
const hook = entry.reported;
|
|
13511
13660
|
if (!hook) return;
|
|
13512
13661
|
entry.reported = void 0;
|
|
13513
|
-
if (
|
|
13662
|
+
if (ctx.status.get(entry.threadId) !== "active") return;
|
|
13514
13663
|
try {
|
|
13515
13664
|
this.#d.report?.({ ...hook, hook_event_name: "PostToolUse" });
|
|
13516
13665
|
} catch {
|
|
13517
13666
|
}
|
|
13518
13667
|
}
|
|
13519
13668
|
/** 服务端请求 → 设备卡片;推不了返回原因(只进日志,不含内容) */
|
|
13520
|
-
#card(method, p, threadId) {
|
|
13521
|
-
const cwd =
|
|
13669
|
+
#card(method, p, threadId, ctx) {
|
|
13670
|
+
const cwd = ctx.cwd.get(threadId) ?? "";
|
|
13522
13671
|
const approve = (reply) => nativeReply(reply, "decision") ?? (reply.decision === "allow" ? { decision: "accept" } : reply.decision === "deny" ? { decision: "decline" } : void 0);
|
|
13523
13672
|
const withOptions = (opts) => opts ? { deskbuddy_options: opts } : {};
|
|
13524
13673
|
if (method === "item/commandExecution/requestApproval") {
|
|
@@ -13531,7 +13680,7 @@ var CodexWatch = class {
|
|
|
13531
13680
|
}
|
|
13532
13681
|
if (method === "item/fileChange/requestApproval") {
|
|
13533
13682
|
if (typeof p.grantRoot === "string" && p.grantRoot) return "\u5E26 grantRoot";
|
|
13534
|
-
const paths = typeof p.itemId === "string" &&
|
|
13683
|
+
const paths = typeof p.itemId === "string" && ctx.changes.get(p.itemId) || [];
|
|
13535
13684
|
const shown = paths.join(", ");
|
|
13536
13685
|
if (!shown) return NO_FILE_LIST;
|
|
13537
13686
|
return { hook: { tool_name: "Edit", tool_input: { file_path: shown, file_paths: [...paths] }, cwd, ...withOptions(codexFileOptions()) }, respond: approve };
|
|
@@ -13553,7 +13702,7 @@ var CodexWatch = class {
|
|
|
13553
13702
|
};
|
|
13554
13703
|
}
|
|
13555
13704
|
if (method === "mcpServer/elicitation/request") {
|
|
13556
|
-
const e = codexElicitation(p,
|
|
13705
|
+
const e = codexElicitation(p, ctx.mcpCalls.get(threadId));
|
|
13557
13706
|
if (typeof e === "string") return e;
|
|
13558
13707
|
if (e.kind === "approval") {
|
|
13559
13708
|
const remember = e.remember;
|
|
@@ -13587,26 +13736,41 @@ var CodexWatch = class {
|
|
|
13587
13736
|
report: true
|
|
13588
13737
|
};
|
|
13589
13738
|
}
|
|
13739
|
+
if (method === "item/permissions/requestApproval") {
|
|
13740
|
+
const c = codexPermissionsCard(p);
|
|
13741
|
+
if (!c) return "\u6743\u9650\u7533\u8BF7\u5185\u5BB9\u8BA4\u4E0D\u51FA";
|
|
13742
|
+
return {
|
|
13743
|
+
hook: { tool_name: CODEX_PERMISSIONS_TOOL, tool_input: c.toolInput, cwd: typeof p.cwd === "string" ? p.cwd : cwd, deskbuddy_summary: c.summary, deskbuddy_options: c.options },
|
|
13744
|
+
respond: (reply) => {
|
|
13745
|
+
const n = reply.native;
|
|
13746
|
+
if (n && typeof n === "object" && "permissions" in n && (reply.decision === "allow" || reply.decision === "deny")) return n;
|
|
13747
|
+
if (reply.decision === "allow") return c.options.actions.allow?.native;
|
|
13748
|
+
if (reply.decision === "deny") return { permissions: {}, scope: "turn" };
|
|
13749
|
+
return void 0;
|
|
13750
|
+
}
|
|
13751
|
+
};
|
|
13752
|
+
}
|
|
13590
13753
|
return "\u4E0D\u5904\u7406\u7684\u8BF7\u6C42\u7C7B\u578B";
|
|
13591
13754
|
}
|
|
13592
|
-
#onServerRequest(id, method, p) {
|
|
13755
|
+
#onServerRequest(id, method, p, ctx) {
|
|
13593
13756
|
const threadId = typeof p.threadId === "string" ? p.threadId : "";
|
|
13594
13757
|
if (!threadId) return;
|
|
13595
|
-
const key =
|
|
13596
|
-
if (
|
|
13758
|
+
const key = ctx.keyOf(threadId, id);
|
|
13759
|
+
if (ctx.pending.get(key)?.threadId === threadId || ctx.standby.get(key)?.threadId === threadId) return;
|
|
13597
13760
|
const entry = { ac: new AbortController(), threadId };
|
|
13598
|
-
|
|
13599
|
-
const
|
|
13600
|
-
const
|
|
13761
|
+
if (typeof p.itemId === "string" && p.itemId) entry.claim = `${threadId}:${p.itemId}`;
|
|
13762
|
+
const skip = (why) => this.#d.log(`${this.#tag(ctx)}\u8BF7\u6C42 ${method}\uFF08\u4F1A\u8BDD ${threadId.slice(0, 8)}\uFF09\u4E0D\u63A8\u8BBE\u5907\uFF1A${why}`);
|
|
13763
|
+
const card = this.#card(method, p, threadId, ctx);
|
|
13764
|
+
const inflight = ctx.inflight(threadId);
|
|
13601
13765
|
if (card === NO_FILE_LIST && inflight) {
|
|
13602
|
-
|
|
13766
|
+
ctx.pending.set(key, entry);
|
|
13603
13767
|
const gen = this.#gen;
|
|
13604
13768
|
void inflight.then(() => void 0).then(() => {
|
|
13605
|
-
if (gen !== this.#gen ||
|
|
13606
|
-
|
|
13607
|
-
const again = this.#card(method, p, threadId);
|
|
13769
|
+
if (gen !== this.#gen || ctx.pending.get(key) !== entry) return;
|
|
13770
|
+
ctx.pending.delete(key);
|
|
13771
|
+
const again = this.#card(method, p, threadId, ctx);
|
|
13608
13772
|
if (typeof again === "string") skip(again);
|
|
13609
|
-
else this.#push(id, entry, again);
|
|
13773
|
+
else this.#push(id, entry, again, ctx);
|
|
13610
13774
|
});
|
|
13611
13775
|
return;
|
|
13612
13776
|
}
|
|
@@ -13614,7 +13778,8 @@ var CodexWatch = class {
|
|
|
13614
13778
|
skip(card);
|
|
13615
13779
|
return;
|
|
13616
13780
|
}
|
|
13617
|
-
this.#push(id, entry, card);
|
|
13781
|
+
this.#push(id, entry, card, ctx);
|
|
13782
|
+
if (ctx.name && ctx.pending.get(key) === entry) this.#d.log(`${this.#tag(ctx)}\u8BF7\u6C42 ${method}\uFF08\u4F1A\u8BDD ${threadId.slice(0, 8)}\uFF09\u5DF2\u63A8\u8BBE\u5907`);
|
|
13618
13783
|
}
|
|
13619
13784
|
/** 设备上「拒绝并说明」的那段话:作为下一轮发进同一会话(会话正忙时排队到空闲) */
|
|
13620
13785
|
#sendFeedback(threadId, text) {
|
|
@@ -13623,10 +13788,24 @@ var CodexWatch = class {
|
|
|
13623
13788
|
() => this.#d.log(`\u8BBE\u5907\u53CD\u9988\u5199\u8FDB Codex \u4F1A\u8BDD ${threadId.slice(0, 8)} \u5931\u8D25`)
|
|
13624
13789
|
);
|
|
13625
13790
|
}
|
|
13626
|
-
#push(id, entry, card) {
|
|
13627
|
-
const key =
|
|
13791
|
+
#push(id, entry, card, ctx) {
|
|
13792
|
+
const key = ctx.keyOf(entry.threadId, id);
|
|
13628
13793
|
const threadId = entry.threadId;
|
|
13629
|
-
this.#
|
|
13794
|
+
if (entry.claim && !this.#claims.claim(entry.claim, entry)) {
|
|
13795
|
+
this.#d.log(`${this.#tag(ctx)}\u8BF7\u6C42\uFF08\u4F1A\u8BDD ${threadId.slice(0, 8)}\uFF09\u5DF2\u7ECF\u4ECE\u53E6\u4E00\u6761\u901A\u9053\u63A8\u8FC7\u8BBE\u5907\uFF0C\u5148\u4E0D\u63A8\u7B2C\u4E8C\u5F20\uFF08\u90A3\u8FB9\u64A4\u5361\u65F6\u8865\u63A8\uFF09`);
|
|
13796
|
+
const claim = entry.claim;
|
|
13797
|
+
const gen = this.#gen;
|
|
13798
|
+
ctx.standby.set(key, entry);
|
|
13799
|
+
this.#claims.wait(claim, entry, () => {
|
|
13800
|
+
if (ctx.standby.get(key) !== entry) return;
|
|
13801
|
+
ctx.standby.delete(key);
|
|
13802
|
+
if (ctx.name ? this.#channels.get(ctx.name) !== ctx : !this.#ws || gen !== this.#gen) return;
|
|
13803
|
+
this.#d.log(`${this.#tag(ctx)}\u8BF7\u6C42\uFF08\u4F1A\u8BDD ${threadId.slice(0, 8)}\uFF09\u53E6\u4E00\u6761\u901A\u9053\u64A4\u5361\u4E86\uFF0C\u8865\u63A8\u8BBE\u5907`);
|
|
13804
|
+
this.#push(id, { ac: new AbortController(), threadId, claim, ...entry.reported ? { reported: entry.reported } : {} }, card, ctx);
|
|
13805
|
+
});
|
|
13806
|
+
return;
|
|
13807
|
+
}
|
|
13808
|
+
ctx.pending.set(key, entry);
|
|
13630
13809
|
const hook = { hook_event_name: "PermissionRequest", session_id: threadId, ...card.hook };
|
|
13631
13810
|
if (card.report) {
|
|
13632
13811
|
entry.reported = hook;
|
|
@@ -13636,21 +13815,23 @@ var CodexWatch = class {
|
|
|
13636
13815
|
}
|
|
13637
13816
|
}
|
|
13638
13817
|
const req = { window: true, tool: "codex", hook };
|
|
13639
|
-
const settled2 = () =>
|
|
13640
|
-
if (this.#idle.has(threadId)) this.#unsubscribe(threadId);
|
|
13641
|
-
};
|
|
13818
|
+
const settled2 = () => ctx.settled(threadId);
|
|
13642
13819
|
void this.#d.gate(req, entry.ac.signal).then((reply) => {
|
|
13643
|
-
if (
|
|
13644
|
-
|
|
13820
|
+
if (ctx.pending.get(key) !== entry || entry.ac.signal.aborted) return;
|
|
13821
|
+
ctx.pending.delete(key);
|
|
13822
|
+
this.#release(entry);
|
|
13645
13823
|
const result = card.respond(reply);
|
|
13646
13824
|
if (result) {
|
|
13647
|
-
|
|
13648
|
-
this.#reportResumed(entry);
|
|
13649
|
-
if (reply.decision === "deny" && typeof reply.feedback === "string" && reply.feedback.trim())
|
|
13825
|
+
ctx.reply(threadId, id, result);
|
|
13826
|
+
this.#reportResumed(entry, ctx);
|
|
13827
|
+
if (reply.decision === "deny" && typeof reply.feedback === "string" && reply.feedback.trim()) ctx.feedback(threadId, reply.feedback);
|
|
13650
13828
|
}
|
|
13651
13829
|
settled2();
|
|
13652
13830
|
}).catch(() => {
|
|
13653
|
-
if (
|
|
13831
|
+
if (ctx.pending.get(key) === entry) {
|
|
13832
|
+
ctx.pending.delete(key);
|
|
13833
|
+
this.#release(entry);
|
|
13834
|
+
}
|
|
13654
13835
|
settled2();
|
|
13655
13836
|
});
|
|
13656
13837
|
}
|
|
@@ -13826,6 +14007,445 @@ function codexQuestionInput(p) {
|
|
|
13826
14007
|
return { toolInput: { questions }, ids };
|
|
13827
14008
|
}
|
|
13828
14009
|
|
|
14010
|
+
// src/daemon/chatgptApp/bridge.ts
|
|
14011
|
+
var INJECT_VERSION = 2;
|
|
14012
|
+
var QUEUE_MAX2 = 500;
|
|
14013
|
+
var RESOLVED_MAX = 200;
|
|
14014
|
+
var KEEPER_EVERY_MS5 = 15 * 1e3;
|
|
14015
|
+
var LAUNCH_CHECK_EVERY_MS2 = 2 * 1e3;
|
|
14016
|
+
var CONNECT_BACKOFF_MIN_MS5 = 15 * 1e3;
|
|
14017
|
+
var CONNECT_BACKOFF_MAX_MS5 = 5 * 60 * 1e3;
|
|
14018
|
+
var LABEL = "ChatGPT App";
|
|
14019
|
+
var CHANNEL = "app";
|
|
14020
|
+
var REQUEST_METHODS = [
|
|
14021
|
+
"item/commandExecution/requestApproval",
|
|
14022
|
+
"item/fileChange/requestApproval",
|
|
14023
|
+
"item/permissions/requestApproval",
|
|
14024
|
+
"mcpServer/elicitation/request",
|
|
14025
|
+
"item/tool/requestUserInput"
|
|
14026
|
+
];
|
|
14027
|
+
var INJECT = `(() => {
|
|
14028
|
+
const V = ${INJECT_VERSION}
|
|
14029
|
+
const w = window
|
|
14030
|
+
const cur = w.__deskbuddyChatgpt
|
|
14031
|
+
if (cur && cur.v === V) return 'ok'
|
|
14032
|
+
if (cur) cur.off = true
|
|
14033
|
+
const M = new Set(${JSON.stringify(REQUEST_METHODS)})
|
|
14034
|
+
const s = { v: V, g: Date.now().toString(36) + Math.random().toString(36).slice(2), q: [], d: 0, r: [], off: false }
|
|
14035
|
+
const push = (item) => {
|
|
14036
|
+
s.q.push(item)
|
|
14037
|
+
if (s.q.length > ${QUEUE_MAX2}) { s.d += s.q.length - ${QUEUE_MAX2}; s.q.splice(0, s.q.length - ${QUEUE_MAX2}) }
|
|
14038
|
+
}
|
|
14039
|
+
w.addEventListener('message', (e) => {
|
|
14040
|
+
try {
|
|
14041
|
+
if (s.off) return
|
|
14042
|
+
const m = e && e.data
|
|
14043
|
+
if (!m || typeof m !== 'object' || m.hostId !== 'local') return
|
|
14044
|
+
if (m.type === 'mcp-request') {
|
|
14045
|
+
const r = m.request
|
|
14046
|
+
if (!r || typeof r !== 'object' || !M.has(r.method)) return
|
|
14047
|
+
// \u540C\u4F1A\u8BDD id \u91CD\u65B0\u4ECE 0 \u7F16\u53F7\uFF08App \u91CD\u542F app-server / \u4F1A\u8BDD\u91CD\u8F7D\uFF09\uFF1A\u8FD9\u4E2A id \u4E4B\u524D\u7684\u4E86\u7ED3\u8BB0\u5F55\u4F5C\u5E9F\uFF0C\u5426\u5219\u65B0\u8BF7\u6C42\u7684\u7B54\u590D\u4F1A\u88AB\u8BEF\u62E6
|
|
14048
|
+
const t = r.params && typeof r.params.threadId === 'string' ? r.params.threadId : '*'
|
|
14049
|
+
const id = String(r.id)
|
|
14050
|
+
s.r = s.r.filter((k) => k !== t + ':' + id && k !== '*:' + id)
|
|
14051
|
+
push({ type: 'mcp-request', request: { id: r.id, method: r.method, params: r.params } })
|
|
14052
|
+
} else if (m.type === 'mcp-notification') {
|
|
14053
|
+
if (typeof m.method !== 'string' || /delta$/i.test(m.method)) return
|
|
14054
|
+
if (m.method === 'serverRequest/resolved' && m.params && typeof m.params === 'object') {
|
|
14055
|
+
const t = typeof m.params.threadId === 'string' ? m.params.threadId : '*'
|
|
14056
|
+
s.r.push(t + ':' + String(m.params.requestId))
|
|
14057
|
+
if (s.r.length > ${RESOLVED_MAX}) s.r.splice(0, s.r.length - ${RESOLVED_MAX})
|
|
14058
|
+
}
|
|
14059
|
+
push({ type: 'mcp-notification', method: m.method, params: m.params })
|
|
14060
|
+
}
|
|
14061
|
+
} catch (_) {}
|
|
14062
|
+
})
|
|
14063
|
+
w.__deskbuddyChatgpt = s
|
|
14064
|
+
return 'injected'
|
|
14065
|
+
})()`;
|
|
14066
|
+
var DRAIN = `(() => {
|
|
14067
|
+
const s = window.__deskbuddyChatgpt
|
|
14068
|
+
if (!s || s.v !== ${INJECT_VERSION} || s.off) return { missing: true }
|
|
14069
|
+
const items = s.q
|
|
14070
|
+
const dropped = s.d
|
|
14071
|
+
s.q = []
|
|
14072
|
+
s.d = 0
|
|
14073
|
+
return { g: s.g, items, dropped }
|
|
14074
|
+
})()`;
|
|
14075
|
+
function answerExpr4(gen, threadId, id, result) {
|
|
14076
|
+
return `(async () => {
|
|
14077
|
+
const s = window.__deskbuddyChatgpt
|
|
14078
|
+
if (!s || s.g !== ${JSON.stringify(gen)}) return { ok: false, why: 'reloaded' }
|
|
14079
|
+
const T = ${JSON.stringify(threadId)}
|
|
14080
|
+
const ID = ${JSON.stringify(id)}
|
|
14081
|
+
if (s.r.indexOf(T + ':' + String(ID)) >= 0 || s.r.indexOf('*:' + String(ID)) >= 0) return { ok: false, why: 'resolved' }
|
|
14082
|
+
const b = window.electronBridge
|
|
14083
|
+
if (!b || typeof b.sendMessageFromView !== 'function') return { ok: false, why: 'no_bridge' }
|
|
14084
|
+
try {
|
|
14085
|
+
await b.sendMessageFromView({ type: 'mcp-response', hostId: 'local', threadId: T, response: { id: ID, result: ${JSON.stringify(result)} } })
|
|
14086
|
+
} catch (e) {
|
|
14087
|
+
return { ok: false, why: 'send_failed', err: String((e && e.message) || e).slice(0, 120) }
|
|
14088
|
+
}
|
|
14089
|
+
return { ok: true }
|
|
14090
|
+
})()`;
|
|
14091
|
+
}
|
|
14092
|
+
function pickChatgptTarget(list) {
|
|
14093
|
+
if (!Array.isArray(list)) return null;
|
|
14094
|
+
for (const t of list) {
|
|
14095
|
+
if (!t || t.type !== "page" || typeof t.webSocketDebuggerUrl !== "string") continue;
|
|
14096
|
+
if (String(t.url ?? "").split(/[?#]/)[0] === "app://-/index.html") return t;
|
|
14097
|
+
}
|
|
14098
|
+
return null;
|
|
14099
|
+
}
|
|
14100
|
+
var ChatgptAppBridge = class {
|
|
14101
|
+
#d;
|
|
14102
|
+
#client = null;
|
|
14103
|
+
#channel = null;
|
|
14104
|
+
/** 当前页面的注入代号('' = 还没取到过) */
|
|
14105
|
+
#gen = "";
|
|
14106
|
+
#portStatus = "down";
|
|
14107
|
+
#portChecked = false;
|
|
14108
|
+
#keeperAt = 0;
|
|
14109
|
+
#launchAt = 0;
|
|
14110
|
+
#launchRunning = false;
|
|
14111
|
+
#connectRetryAt = 0;
|
|
14112
|
+
#connectBackoffMs = CONNECT_BACKOFF_MIN_MS5;
|
|
14113
|
+
#connectFailLogged = false;
|
|
14114
|
+
#running = false;
|
|
14115
|
+
#stopped = false;
|
|
14116
|
+
/** 会话 → 最近一次状态(thread/status/changed);有 active 的 = App 忙 */
|
|
14117
|
+
#status = /* @__PURE__ */ new Map();
|
|
14118
|
+
constructor(deps2) {
|
|
14119
|
+
this.#d = deps2;
|
|
14120
|
+
}
|
|
14121
|
+
#now() {
|
|
14122
|
+
return this.#d.now ? this.#d.now() : Date.now();
|
|
14123
|
+
}
|
|
14124
|
+
get connected() {
|
|
14125
|
+
return !!this.#client && !this.#client.closed;
|
|
14126
|
+
}
|
|
14127
|
+
/** 页面里有进行中的会话(keeper 判忙用;断开时清空) */
|
|
14128
|
+
busy() {
|
|
14129
|
+
for (const t of this.#status.values()) if (t === "active") return true;
|
|
14130
|
+
return false;
|
|
14131
|
+
}
|
|
14132
|
+
/** 等设备答复的卡有几张 */
|
|
14133
|
+
pending() {
|
|
14134
|
+
return this.#channel?.pending() ?? 0;
|
|
14135
|
+
}
|
|
14136
|
+
stop() {
|
|
14137
|
+
this.#stopped = true;
|
|
14138
|
+
this.#disconnect();
|
|
14139
|
+
}
|
|
14140
|
+
/** daemon 每秒调一次;重入直接返回。不抛 */
|
|
14141
|
+
async tick() {
|
|
14142
|
+
if (this.#running || this.#stopped) return;
|
|
14143
|
+
this.#running = true;
|
|
14144
|
+
try {
|
|
14145
|
+
await this.#tick();
|
|
14146
|
+
} catch (e) {
|
|
14147
|
+
this.#d.log(`${LABEL} \u6388\u6743\u6865\u51FA\u9519\uFF1A${e?.message ?? e}`);
|
|
14148
|
+
} finally {
|
|
14149
|
+
this.#running = false;
|
|
14150
|
+
}
|
|
14151
|
+
}
|
|
14152
|
+
async #tick() {
|
|
14153
|
+
if (!this.#d.enabled()) {
|
|
14154
|
+
this.#disconnect();
|
|
14155
|
+
return;
|
|
14156
|
+
}
|
|
14157
|
+
const now = this.#now();
|
|
14158
|
+
if (this.#portStatus !== "up") this.#checkLaunch(now);
|
|
14159
|
+
if (this.#keeperAt === 0 || now - this.#keeperAt >= KEEPER_EVERY_MS5) {
|
|
14160
|
+
this.#keeperAt = now;
|
|
14161
|
+
const prev = this.#portChecked ? this.#portStatus : null;
|
|
14162
|
+
this.#portStatus = await this.#d.keeper.tick();
|
|
14163
|
+
this.#portChecked = true;
|
|
14164
|
+
if (this.#stopped) return this.#disconnect();
|
|
14165
|
+
if (this.#portStatus !== prev) {
|
|
14166
|
+
const p = this.#d.port();
|
|
14167
|
+
if (this.#portStatus === "down") this.#d.log(`${LABEL} \u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF08${p} \u4E0D\u901A\uFF0C\u6216 App \u6CA1\u5F00\uFF09\uFF0CApp \u91CC Codex \u7684\u5F85\u786E\u8BA4\u63A8\u4E0D\u5230\u8BBE\u5907`);
|
|
14168
|
+
else if (this.#portStatus === "untrusted") this.#d.log(`${LABEL} \u8C03\u8BD5\u7AEF\u53E3 ${p} \u7684\u76D1\u542C\u8005\u4E0D\u662F\u672C\u673A\u56DE\u73AF\u4E0A\u7684 ChatGPT App\uFF0C\u4E0D\u8FDE`);
|
|
14169
|
+
else if (this.#portStatus === "hung") this.#d.log(`${LABEL} \u8C03\u8BD5\u7AEF\u53E3 ${p} \u5728\u76D1\u542C\u4F46\u65E0\u54CD\u5E94`);
|
|
14170
|
+
}
|
|
14171
|
+
if (this.#portStatus !== "up" && this.#client) {
|
|
14172
|
+
this.#d.log(`${LABEL} \u6388\u6743\u6865\u65AD\u5F00\uFF08\u8C03\u8BD5\u7AEF\u53E3${this.#portStatus === "hung" ? "\u65E0\u54CD\u5E94" : this.#portStatus === "untrusted" ? "\u76D1\u542C\u8005\u4E0D\u5BF9" : "\u4E0D\u901A"}\uFF09`);
|
|
14173
|
+
this.#disconnect();
|
|
14174
|
+
}
|
|
14175
|
+
}
|
|
14176
|
+
if (this.#portStatus !== "up") return;
|
|
14177
|
+
if (!this.#client || this.#client.closed) {
|
|
14178
|
+
if (this.#client) {
|
|
14179
|
+
this.#d.log(`${LABEL} \u6388\u6743\u6865\u8FDE\u63A5\u65AD\u4E86\uFF0C\u91CD\u8FDE`);
|
|
14180
|
+
this.#disconnect();
|
|
14181
|
+
}
|
|
14182
|
+
if (now < this.#connectRetryAt) return;
|
|
14183
|
+
if (!await this.#connect()) return;
|
|
14184
|
+
}
|
|
14185
|
+
await this.#poll();
|
|
14186
|
+
}
|
|
14187
|
+
/** 刚启动补端口:不等它(退出 + 拉起要十几秒),keeper 自己防并发;重开了催一下健康检查 */
|
|
14188
|
+
#checkLaunch(now) {
|
|
14189
|
+
if (this.#launchRunning || now - this.#launchAt < LAUNCH_CHECK_EVERY_MS2) return;
|
|
14190
|
+
this.#launchAt = now;
|
|
14191
|
+
this.#launchRunning = true;
|
|
14192
|
+
void this.#d.keeper.fixFreshLaunch().then((r) => {
|
|
14193
|
+
if (r === "reopened") {
|
|
14194
|
+
this.#keeperAt = 0;
|
|
14195
|
+
this.#connectRetryAt = 0;
|
|
14196
|
+
}
|
|
14197
|
+
}).catch((e) => this.#d.log(`${LABEL} \u542F\u52A8\u8865\u7AEF\u53E3\u51FA\u9519\uFF1A${e?.message ?? e}`)).finally(() => {
|
|
14198
|
+
this.#launchRunning = false;
|
|
14199
|
+
});
|
|
14200
|
+
}
|
|
14201
|
+
async #connect() {
|
|
14202
|
+
let client;
|
|
14203
|
+
try {
|
|
14204
|
+
client = await (this.#d.connect ?? ((p) => connectCdp(p, { pick: pickChatgptTarget })))(this.#d.port());
|
|
14205
|
+
} catch (e) {
|
|
14206
|
+
this.#connectRetryAt = this.#now() + this.#connectBackoffMs;
|
|
14207
|
+
this.#connectBackoffMs = Math.min(this.#connectBackoffMs * 2, CONNECT_BACKOFF_MAX_MS5);
|
|
14208
|
+
if (!this.#connectFailLogged) {
|
|
14209
|
+
this.#connectFailLogged = true;
|
|
14210
|
+
this.#d.log(`${LABEL} \u6388\u6743\u6865\u8FDE\u4E0D\u4E0A\uFF1A${e?.message ?? e}\uFF08\u5C06\u9000\u907F\u91CD\u8BD5\uFF09`);
|
|
14211
|
+
}
|
|
14212
|
+
return false;
|
|
14213
|
+
}
|
|
14214
|
+
if (this.#stopped) {
|
|
14215
|
+
client.close();
|
|
14216
|
+
return false;
|
|
14217
|
+
}
|
|
14218
|
+
this.#client = client;
|
|
14219
|
+
this.#gen = "";
|
|
14220
|
+
this.#connectBackoffMs = CONNECT_BACKOFF_MIN_MS5;
|
|
14221
|
+
this.#connectRetryAt = 0;
|
|
14222
|
+
this.#connectFailLogged = false;
|
|
14223
|
+
try {
|
|
14224
|
+
await client.call?.("Page.addScriptToEvaluateOnNewDocument", { source: INJECT });
|
|
14225
|
+
} catch {
|
|
14226
|
+
}
|
|
14227
|
+
await this.#eval(INJECT);
|
|
14228
|
+
if (!this.#client) return false;
|
|
14229
|
+
this.#attach();
|
|
14230
|
+
this.#d.log(`${LABEL} \u6388\u6743\u6865\u5DF2\u8FDE\u4E0A\uFF08\u7AEF\u53E3 ${this.#d.port()}\uFF09\uFF0CApp \u91CC Codex \u7684\u5F85\u786E\u8BA4\u53EF\u63A8\u5230\u8BBE\u5907`);
|
|
14231
|
+
return true;
|
|
14232
|
+
}
|
|
14233
|
+
#attach() {
|
|
14234
|
+
this.#channel?.close();
|
|
14235
|
+
this.#channel = this.#d.codex.attachChannel(CHANNEL, {
|
|
14236
|
+
label: LABEL,
|
|
14237
|
+
respond: (threadId, id, result) => void this.#answer(threadId, id, result)
|
|
14238
|
+
});
|
|
14239
|
+
}
|
|
14240
|
+
async #poll() {
|
|
14241
|
+
const r = await this.#eval(DRAIN);
|
|
14242
|
+
if (!this.#client || !r) return;
|
|
14243
|
+
if (r.missing || typeof r.g !== "string") {
|
|
14244
|
+
this.#d.log(`${LABEL} \u9875\u9762\u5237\u65B0\u8FC7\uFF0C\u91CD\u65B0\u6CE8\u5165\u76D1\u542C`);
|
|
14245
|
+
await this.#eval(INJECT);
|
|
14246
|
+
if (!this.#client) return;
|
|
14247
|
+
this.#gen = "";
|
|
14248
|
+
this.#attach();
|
|
14249
|
+
return;
|
|
14250
|
+
}
|
|
14251
|
+
if (this.#gen && r.g !== this.#gen) {
|
|
14252
|
+
this.#d.log(`${LABEL} \u9875\u9762\u5237\u65B0\u8FC7\uFF08\u76D1\u542C\u5DF2\u968F\u65B0\u9875\u9762\u88C5\u4E0A\uFF09\uFF0C\u64A4\u6389\u65E7\u9875\u9762\u4E0A\u7684\u5361`);
|
|
14253
|
+
this.#status.clear();
|
|
14254
|
+
this.#attach();
|
|
14255
|
+
}
|
|
14256
|
+
this.#gen = r.g;
|
|
14257
|
+
if (typeof r.dropped === "number" && r.dropped > 0) this.#d.log(`${LABEL} \u9875\u9762\u6D88\u606F\u961F\u5217\u6EE1\uFF0C\u4E22\u4E86\u6700\u65E7\u7684 ${r.dropped} \u6761`);
|
|
14258
|
+
if (!Array.isArray(r.items)) return;
|
|
14259
|
+
for (const it of r.items) this.#feed(it);
|
|
14260
|
+
}
|
|
14261
|
+
#feed(raw) {
|
|
14262
|
+
const ch = this.#channel;
|
|
14263
|
+
if (!ch || !raw || typeof raw !== "object") return;
|
|
14264
|
+
const m = raw;
|
|
14265
|
+
if (m.type === "mcp-request") {
|
|
14266
|
+
const req = m.request ?? {};
|
|
14267
|
+
const id = req.id;
|
|
14268
|
+
if (typeof req.method !== "string" || !REQUEST_METHODS.includes(req.method)) return;
|
|
14269
|
+
if (typeof id !== "number" && typeof id !== "string") return;
|
|
14270
|
+
ch.onMessage({ id, method: req.method, params: req.params });
|
|
14271
|
+
return;
|
|
14272
|
+
}
|
|
14273
|
+
if (m.type === "mcp-notification" && typeof m.method === "string") {
|
|
14274
|
+
const p = m.params && typeof m.params === "object" ? m.params : {};
|
|
14275
|
+
if (m.method === "thread/status/changed" && typeof p.threadId === "string") {
|
|
14276
|
+
const type2 = (p.status ?? {}).type;
|
|
14277
|
+
if (typeof type2 === "string") this.#status.set(p.threadId, type2);
|
|
14278
|
+
} else if (m.method === "thread/closed" && typeof p.threadId === "string") this.#status.delete(p.threadId);
|
|
14279
|
+
ch.onMessage({ method: m.method, params: p });
|
|
14280
|
+
}
|
|
14281
|
+
}
|
|
14282
|
+
async #answer(threadId, id, result) {
|
|
14283
|
+
const sid = threadId.slice(0, 8);
|
|
14284
|
+
const gen = this.#gen;
|
|
14285
|
+
if (!this.#client || !gen) {
|
|
14286
|
+
this.#d.log(`${LABEL} \u4F1A\u8BDD ${sid}\uFF1A\u6388\u6743\u6865\u5DF2\u65AD\u5F00\uFF0C\u8BBE\u5907\u7B54\u590D\u6CA1\u6709\u53D1\u51FA`);
|
|
14287
|
+
return;
|
|
14288
|
+
}
|
|
14289
|
+
const r = await this.#eval(answerExpr4(gen, threadId, id, result));
|
|
14290
|
+
if (r?.ok) this.#d.log(`${LABEL} \u4F1A\u8BDD ${sid}\uFF1A\u5DF2\u6309\u8BBE\u5907\u7B54\u590D`);
|
|
14291
|
+
else if (r?.why === "resolved") this.#d.log(`${LABEL} \u4F1A\u8BDD ${sid}\uFF1A\u7535\u8111\u4E0A\u5DF2\u5904\u7406\uFF0C\u8BBE\u5907\u7B54\u590D\u6CA1\u6709\u53D1\u51FA`);
|
|
14292
|
+
else if (r?.why === "reloaded") this.#d.log(`${LABEL} \u4F1A\u8BDD ${sid}\uFF1A\u9875\u9762\u5237\u65B0\u8FC7\uFF0C\u8BBE\u5907\u7B54\u590D\u6CA1\u6709\u53D1\u51FA`);
|
|
14293
|
+
else if (r?.why === "no_bridge") this.#d.log(`${LABEL} \u4F1A\u8BDD ${sid}\uFF1A\u9875\u9762\u91CC\u6CA1\u6709 electronBridge.sendMessageFromView\uFF0C\u8BBE\u5907\u7B54\u590D\u6CA1\u6709\u53D1\u51FA`);
|
|
14294
|
+
else this.#d.log(`${LABEL} \u4F1A\u8BDD ${sid}\uFF1A\u8BBE\u5907\u7B54\u590D\u53D1\u9001\u5931\u8D25${r?.err ? `\uFF08${r.err}\uFF09` : ""}`);
|
|
14295
|
+
}
|
|
14296
|
+
async #eval(expr) {
|
|
14297
|
+
const c = this.#client;
|
|
14298
|
+
if (!c) return null;
|
|
14299
|
+
try {
|
|
14300
|
+
return await c.evaluate(expr);
|
|
14301
|
+
} catch (e) {
|
|
14302
|
+
if (c.closed) this.#disconnect();
|
|
14303
|
+
else this.#d.log(`${LABEL} \u9875\u9762\u811A\u672C\u51FA\u9519\uFF1A${e?.message ?? e}`);
|
|
14304
|
+
return null;
|
|
14305
|
+
}
|
|
14306
|
+
}
|
|
14307
|
+
#disconnect() {
|
|
14308
|
+
this.#channel?.close();
|
|
14309
|
+
this.#channel = null;
|
|
14310
|
+
this.#status.clear();
|
|
14311
|
+
this.#gen = "";
|
|
14312
|
+
try {
|
|
14313
|
+
this.#client?.close();
|
|
14314
|
+
} catch {
|
|
14315
|
+
}
|
|
14316
|
+
this.#client = null;
|
|
14317
|
+
}
|
|
14318
|
+
};
|
|
14319
|
+
|
|
14320
|
+
// src/daemon/chatgptApp/keeper.ts
|
|
14321
|
+
import { lstatSync as lstatSync3, readdirSync as readdirSync9 } from "fs";
|
|
14322
|
+
import { join as join20 } from "path";
|
|
14323
|
+
var CHATGPT_DEFAULT_CDP_PORT = 39277;
|
|
14324
|
+
var CHATGPT_DOWN_ROUNDS = 2;
|
|
14325
|
+
var CHATGPT_BRAND = {
|
|
14326
|
+
tool: "chatgpt_app",
|
|
14327
|
+
label: "ChatGPT App",
|
|
14328
|
+
macApp: "/Applications/ChatGPT.app",
|
|
14329
|
+
// 主进程:可执行文件正好是它、只跟 `-` 开头的开关、没有 --type=(排除 helper 与 CodexCLI.app 里的 codex)
|
|
14330
|
+
macExe: "/Applications/ChatGPT.app/Contents/MacOS/ChatGPT",
|
|
14331
|
+
appName: "ChatGPT",
|
|
14332
|
+
// 26.1007 的 Info.plist:CFBundleIdentifier = com.openai.codex
|
|
14333
|
+
bundleId: "com.openai.codex",
|
|
14334
|
+
// package.json productName = Codex(Electron 默认 UA 带 `<应用名>/<版本>`);也认 ChatGPT 以防改名
|
|
14335
|
+
ua: /\b(Codex|ChatGPT)\/\d/,
|
|
14336
|
+
comm: /\/ChatGPT\.app\//,
|
|
14337
|
+
defaultPort: CHATGPT_DEFAULT_CDP_PORT
|
|
14338
|
+
};
|
|
14339
|
+
var CODEX_ACTIVE_MS = 5 * 60 * 1e3;
|
|
14340
|
+
var ROLLOUT_MAX_ENTRIES = 2e4;
|
|
14341
|
+
function codexRolloutsActive(dir2 = join20(codexHome(), "sessions"), now = Date.now()) {
|
|
14342
|
+
let seen = 0;
|
|
14343
|
+
let overflow = false;
|
|
14344
|
+
const walk = (d, depth) => {
|
|
14345
|
+
let names;
|
|
14346
|
+
try {
|
|
14347
|
+
names = readdirSync9(d);
|
|
14348
|
+
} catch {
|
|
14349
|
+
return false;
|
|
14350
|
+
}
|
|
14351
|
+
names.sort().reverse();
|
|
14352
|
+
for (const n of names) {
|
|
14353
|
+
if (++seen > ROLLOUT_MAX_ENTRIES) {
|
|
14354
|
+
overflow = true;
|
|
14355
|
+
return false;
|
|
14356
|
+
}
|
|
14357
|
+
const p = join20(d, n);
|
|
14358
|
+
let st;
|
|
14359
|
+
try {
|
|
14360
|
+
st = lstatSync3(p);
|
|
14361
|
+
} catch {
|
|
14362
|
+
continue;
|
|
14363
|
+
}
|
|
14364
|
+
if (st.isDirectory()) {
|
|
14365
|
+
if (depth < 4 && walk(p, depth + 1)) return true;
|
|
14366
|
+
if (overflow) return false;
|
|
14367
|
+
} else if (n.endsWith(".jsonl") && now - st.mtimeMs < CODEX_ACTIVE_MS) return true;
|
|
14368
|
+
}
|
|
14369
|
+
return false;
|
|
14370
|
+
};
|
|
14371
|
+
return walk(dir2, 0) || overflow;
|
|
14372
|
+
}
|
|
14373
|
+
function chatgptKeeperThrottleFile() {
|
|
14374
|
+
return join20(deskbuddyHome(), "chatgptapp", "keeper.json");
|
|
14375
|
+
}
|
|
14376
|
+
var ChatgptKeeper = class {
|
|
14377
|
+
#d;
|
|
14378
|
+
#inner;
|
|
14379
|
+
#down = 0;
|
|
14380
|
+
#lastResult = "";
|
|
14381
|
+
constructor(deps2) {
|
|
14382
|
+
this.#d = deps2;
|
|
14383
|
+
this.#inner = deps2.inner ?? new QoderCdpKeeper({
|
|
14384
|
+
...deps2,
|
|
14385
|
+
brand: CHATGPT_BRAND,
|
|
14386
|
+
throttleFile: deps2.throttleFile ?? chatgptKeeperThrottleFile,
|
|
14387
|
+
skipSystem: deps2.skipSystem ?? (() => process.env.DESKBUDDY_SKIP_CHATGPT_CDP === "1"),
|
|
14388
|
+
// 忙:App 页面里有进行中的会话(桥的最近状态)/ Codex hook 报着在跑或等授权 / Codex 会话记录 5 分钟内写过 / 有等设备的卡
|
|
14389
|
+
busy: () => (deps2.busy ? deps2.busy() : toolBusy("codex") || codexRolloutsActive()) || deps2.hasCards?.() === true
|
|
14390
|
+
});
|
|
14391
|
+
}
|
|
14392
|
+
#platform() {
|
|
14393
|
+
return this.#d.platform ?? process.platform;
|
|
14394
|
+
}
|
|
14395
|
+
#skip() {
|
|
14396
|
+
return !this.#d.inner && (this.#d.skipSystem ? this.#d.skipSystem() : process.env.DESKBUDDY_SKIP_CHATGPT_CDP === "1");
|
|
14397
|
+
}
|
|
14398
|
+
#manage() {
|
|
14399
|
+
try {
|
|
14400
|
+
return this.#d.manage ? this.#d.manage() !== false : true;
|
|
14401
|
+
} catch {
|
|
14402
|
+
return false;
|
|
14403
|
+
}
|
|
14404
|
+
}
|
|
14405
|
+
async tick() {
|
|
14406
|
+
if (this.#platform() !== "darwin") return "untrusted";
|
|
14407
|
+
if (this.#skip()) return "down";
|
|
14408
|
+
const st = await this.#inner.tick();
|
|
14409
|
+
if (st !== "down") {
|
|
14410
|
+
this.#down = 0;
|
|
14411
|
+
return st;
|
|
14412
|
+
}
|
|
14413
|
+
this.#down++;
|
|
14414
|
+
if (this.#down >= CHATGPT_DOWN_ROUNDS) await this.#keepOpen().catch((e) => this.#d.log(`ChatGPT App \u8C03\u8BD5\u7AEF\u53E3\uFF1A\u91CD\u5F00\u51FA\u9519 ${e?.message ?? e}`));
|
|
14415
|
+
return "down";
|
|
14416
|
+
}
|
|
14417
|
+
/** App 刚启动(不到 10 秒)没带端口:立即带端口重开一次(Qoder keeper 的同一套,单独限流) */
|
|
14418
|
+
async fixFreshLaunch() {
|
|
14419
|
+
if (this.#platform() !== "darwin" || this.#skip() || !this.#manage()) return "skipped";
|
|
14420
|
+
return this.#inner.fixFreshLaunch();
|
|
14421
|
+
}
|
|
14422
|
+
async #keepOpen() {
|
|
14423
|
+
if (!this.#manage()) return;
|
|
14424
|
+
if (this.#d.hasCards?.() === true) return;
|
|
14425
|
+
if (!await this.#inner.appRunning()) {
|
|
14426
|
+
if (this.#lastResult !== "not_running") {
|
|
14427
|
+
this.#lastResult = "not_running";
|
|
14428
|
+
this.#d.log(`ChatGPT App \u6CA1\u5728\u8FD0\u884C\uFF08\u8C03\u8BD5\u7AEF\u53E3 ${this.#d.port()} \u4E0D\u5728\uFF09\uFF0CApp \u91CC Codex \u7684\u5F85\u786E\u8BA4\u63A8\u4E0D\u5230\u8BBE\u5907`);
|
|
14429
|
+
}
|
|
14430
|
+
return;
|
|
14431
|
+
}
|
|
14432
|
+
const r = await this.#inner.wakeForTask("keep_open");
|
|
14433
|
+
if (r === "restarted") this.#down = 0;
|
|
14434
|
+
if (r === this.#lastResult) return;
|
|
14435
|
+
this.#lastResult = r;
|
|
14436
|
+
const why = {
|
|
14437
|
+
hung: "\u7AEF\u53E3\u5728\u76D1\u542C\u4F46\u65E0\u54CD\u5E94\uFF08App \u53EF\u80FD\u5361\u4F4F\u4E86\uFF09\uFF0C\u6309\u5361\u6B7B\u7B56\u7565\u5904\u7406",
|
|
14438
|
+
restarted: "\u5DF2\u9000\u51FA\u5E76\u5E26\u7AEF\u53E3\u91CD\u5F00",
|
|
14439
|
+
busy: "\u6709\u4F1A\u8BDD\u5728\u8DD1\u6216\u6709\u7B49\u8BBE\u5907\u7684\u5361\uFF0C\u7B49\u7A7A\u95F2\u518D\u91CD\u5F00",
|
|
14440
|
+
present: "\u7528\u6237\u5728\u7535\u8111\u524D\uFF0C\u4E0D\u6253\u65AD\uFF08ChatGPT App \u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF0CApp \u91CC Codex \u7684\u5F85\u786E\u8BA4\u63A8\u4E0D\u5230\u8BBE\u5907\uFF09",
|
|
14441
|
+
failed: "\u91CD\u5F00\u5931\u8D25\u6216\u4ECA\u5929\u7684\u91CD\u5F00\u6B21\u6570\u5DF2\u7528\u5B8C",
|
|
14442
|
+
launched: "\u521A\u62C9\u8D77\u8FC7\uFF0C\u7B49\u5B83\u8D77\u6765",
|
|
14443
|
+
not_installed: "\u6CA1\u627E\u5230 App \u672C\u4F53"
|
|
14444
|
+
};
|
|
14445
|
+
if (why[r]) this.#d.log(`ChatGPT App \u8C03\u8BD5\u7AEF\u53E3 ${this.#d.port()} \u4E0D\u5728\uFF1A${why[r]}`);
|
|
14446
|
+
}
|
|
14447
|
+
};
|
|
14448
|
+
|
|
13829
14449
|
// src/core/codexRemote.ts
|
|
13830
14450
|
import { mkdirSync as mkdirSync10, renameSync as renameSync7, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
13831
14451
|
import { join as join21 } from "path";
|
|
@@ -13849,7 +14469,7 @@ function removeCodexRemote() {
|
|
|
13849
14469
|
}
|
|
13850
14470
|
|
|
13851
14471
|
// src/core/interrupt.ts
|
|
13852
|
-
import { closeSync as closeSync8, openSync as openSync8, readdirSync as
|
|
14472
|
+
import { closeSync as closeSync8, openSync as openSync8, readdirSync as readdirSync10, readSync as readSync7, statSync as statSync11 } from "fs";
|
|
13853
14473
|
import { dirname as dirname6, join as join22 } from "path";
|
|
13854
14474
|
var INTERRUPT_TOOLS = ["claude_code", "qoder_cn", "qoder", "qwenwork"];
|
|
13855
14475
|
var CODEX_INTERRUPT_TOOL = "codex";
|
|
@@ -13963,7 +14583,7 @@ function claudeTranscriptFile(sessionId, cwd, home = claudeHome()) {
|
|
|
13963
14583
|
}
|
|
13964
14584
|
let dirs = [];
|
|
13965
14585
|
try {
|
|
13966
|
-
dirs =
|
|
14586
|
+
dirs = readdirSync10(join22(home, "projects"));
|
|
13967
14587
|
} catch {
|
|
13968
14588
|
return null;
|
|
13969
14589
|
}
|
|
@@ -14960,7 +15580,7 @@ function resultSummary(tool, stdout6) {
|
|
|
14960
15580
|
}
|
|
14961
15581
|
|
|
14962
15582
|
// src/core/dshMailbox.ts
|
|
14963
|
-
import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync12, readdirSync as
|
|
15583
|
+
import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync12, readdirSync as readdirSync11, readFileSync as readFileSync13, renameSync as renameSync9, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "fs";
|
|
14964
15584
|
import { join as join27 } from "path";
|
|
14965
15585
|
var DSH_HEARTBEAT_FRESH_MS = 5 * 1e3;
|
|
14966
15586
|
var DSH_ACK_TIMEOUT_MS = 8 * 1e3;
|
|
@@ -15006,7 +15626,7 @@ function liveHeartbeats(sessionId, d0 = {}) {
|
|
|
15006
15626
|
const dir2 = sub(d.home, "alive", d.channel);
|
|
15007
15627
|
let names;
|
|
15008
15628
|
try {
|
|
15009
|
-
names =
|
|
15629
|
+
names = readdirSync11(dir2);
|
|
15010
15630
|
} catch {
|
|
15011
15631
|
return [];
|
|
15012
15632
|
}
|
|
@@ -15131,7 +15751,7 @@ function dshClearAcks(home = deskbuddyHome(), channel = "dsh") {
|
|
|
15131
15751
|
function clearDir(inbox) {
|
|
15132
15752
|
let names;
|
|
15133
15753
|
try {
|
|
15134
|
-
names =
|
|
15754
|
+
names = readdirSync11(inbox);
|
|
15135
15755
|
} catch {
|
|
15136
15756
|
return 0;
|
|
15137
15757
|
}
|
|
@@ -15305,6 +15925,12 @@ var Daemon = class {
|
|
|
15305
15925
|
#wakeReport = null;
|
|
15306
15926
|
/** url → 连着它的 CodexWatch。插入顺序 = 发现顺序(DeskBuddy 管的在前) */
|
|
15307
15927
|
#codex = /* @__PURE__ */ new Map();
|
|
15928
|
+
/** Codex 待确认去重(threadId:itemId):ChatGPT App 页面桥与各 ws app-server 共用一份,同一条只推一张 */
|
|
15929
|
+
#codexClaims = new CodexClaims();
|
|
15930
|
+
/** ChatGPT 桌面 App 里 Codex 的待确认(spec 2026-10-12 chatgpt-app-bridge):不连 ws,只挂页面桥这条额外通道 */
|
|
15931
|
+
#chatgptCodex;
|
|
15932
|
+
#chatgptApp;
|
|
15933
|
+
#chatgptAppTimer = null;
|
|
15308
15934
|
/** 千问办公的选择题 / 授权推设备(spec 2026-10-03 §3.1):hook 发 notify 进来,回填走 App 内置 MCP */
|
|
15309
15935
|
#qwenwork;
|
|
15310
15936
|
/** 千问办公 App 自弹的确认框(spec 2026-10-10 qwenwork-newtask-appcards §4);与 #qwenwork 共用认领表,一张确认只推一张 */
|
|
@@ -15342,6 +15968,9 @@ var Daemon = class {
|
|
|
15342
15968
|
/** Qoder / Qoder CN 桌面 App 里的选择题推设备(spec 2026-10-11 qoder-app-question),每品牌一份 */
|
|
15343
15969
|
#qoderQuestions = /* @__PURE__ */ new Map();
|
|
15344
15970
|
#qoderLnkAt = 0;
|
|
15971
|
+
#qwLnkAt = 0;
|
|
15972
|
+
/** 千问办公新启动后补快捷方式(App 启动约 1 秒后会清空自己快捷方式的参数,Bug 表 274) */
|
|
15973
|
+
#qwRelaunch = null;
|
|
15345
15974
|
#winLocateTimer = null;
|
|
15346
15975
|
#interruptTimer = null;
|
|
15347
15976
|
/** 会话 → transcript 路径(打断扫描用,找一次记住) */
|
|
@@ -15414,6 +16043,7 @@ var Daemon = class {
|
|
|
15414
16043
|
ocLive: deps2.ocLive ?? ((sid) => ocLiveFor(sid) != null),
|
|
15415
16044
|
appReply: deps2.appReply ?? ((tool, sid, cwd, text, exp) => tool === "dumate" ? this.#dumate.deliverReply(sid, text, exp) : this.#qwenwork.reply(cwd, text, sid)),
|
|
15416
16045
|
appNewTask: deps2.appNewTask ?? ((_tool, id, text, dirs) => this.#qwenwork.newTask(id, text, dirs)),
|
|
16046
|
+
qwPortMissing: deps2.qwPortMissing ?? (() => this.#qwApp.portMissing()),
|
|
15417
16047
|
dumateNewTask: deps2.dumateNewTask ?? ((cwd, text, iid, exp) => this.#dumate.startTask(cwd, text, iid, exp)),
|
|
15418
16048
|
dumateNewTaskEnabled: deps2.dumateNewTaskEnabled ?? (() => process.platform === "win32" || this.#dmCdp.target() !== null),
|
|
15419
16049
|
ocDeliver: deps2.ocDeliver ?? ((sid, msg) => ocDeliver(sid, msg)),
|
|
@@ -15542,6 +16172,30 @@ var Daemon = class {
|
|
|
15542
16172
|
},
|
|
15543
16173
|
port: () => loadConfig().workbuddyCdpPort ?? DEFAULT_CDP_PORT
|
|
15544
16174
|
});
|
|
16175
|
+
this.#chatgptCodex = new CodexWatch({
|
|
16176
|
+
gate: (req, signal) => this.#coord.onGate(req, signal),
|
|
16177
|
+
log,
|
|
16178
|
+
discover: () => null,
|
|
16179
|
+
// MCP 授权 Codex 不触发 hook:同 ws 那一路替它报待确认
|
|
16180
|
+
report: (input) => runCodexHook(this.#deps.nodePath, input),
|
|
16181
|
+
claims: this.#codexClaims
|
|
16182
|
+
});
|
|
16183
|
+
const chatgptPort = () => loadConfig().chatgptCdpPort ?? CHATGPT_DEFAULT_CDP_PORT;
|
|
16184
|
+
this.#chatgptApp = new ChatgptAppBridge({
|
|
16185
|
+
log,
|
|
16186
|
+
// 一期只做 macOS;卡片走 codex,须启用 codex
|
|
16187
|
+
enabled: () => process.platform === "darwin" && loadConfig().enabledTools.includes("codex"),
|
|
16188
|
+
port: chatgptPort,
|
|
16189
|
+
codex: this.#chatgptCodex,
|
|
16190
|
+
keeper: new ChatgptKeeper({
|
|
16191
|
+
port: chatgptPort,
|
|
16192
|
+
log,
|
|
16193
|
+
manage: () => loadConfig().chatgptAppPort !== false,
|
|
16194
|
+
// 端口不通时页面状态看不到:再看 Codex hook 状态与会话记录的修改时间(App 里 hook 没被信任时只有后者)
|
|
16195
|
+
busy: () => this.#chatgptApp.busy() || toolBusy("codex") || codexRolloutsActive(),
|
|
16196
|
+
hasCards: () => this.#chatgptApp.pending() > 0
|
|
16197
|
+
})
|
|
16198
|
+
});
|
|
15545
16199
|
this.#wbTasks = deps2.wbAppTasks ?? new WbAppTasks({
|
|
15546
16200
|
eval: (expr) => this.#wbApp.appEval(expr),
|
|
15547
16201
|
available: () => this.#wbApp.available(),
|
|
@@ -15654,7 +16308,24 @@ var Daemon = class {
|
|
|
15654
16308
|
this.#dumateTimer.unref();
|
|
15655
16309
|
this.#wbAppTimer = setInterval(() => void this.#wbApp.tick(), WB_APP_WATCH_MS);
|
|
15656
16310
|
this.#wbAppTimer.unref();
|
|
15657
|
-
this.#
|
|
16311
|
+
this.#chatgptAppTimer = setInterval(() => void this.#chatgptApp.tick(), WB_APP_WATCH_MS);
|
|
16312
|
+
this.#chatgptAppTimer.unref();
|
|
16313
|
+
const qwPidKeeper = new QwenworkKeeper({ port: () => loadConfig().qwenworkCdpPort ?? QW_DEFAULT_CDP_PORT, log });
|
|
16314
|
+
this.#qwRelaunch = new QwShortcutRelaunchFixer({
|
|
16315
|
+
now: () => this.#deps.now(),
|
|
16316
|
+
wanted: qwenworkShortcutsWanted,
|
|
16317
|
+
mainPid: () => qwPidKeeper.winMainPid(),
|
|
16318
|
+
ensure: () => ensureQwenworkShortcuts({ log })
|
|
16319
|
+
});
|
|
16320
|
+
this.#qwAppTimer = setInterval(() => {
|
|
16321
|
+
void this.#qwApp.tick();
|
|
16322
|
+
const now = this.#deps.now();
|
|
16323
|
+
if (now - this.#qwLnkAt >= QW_SHORTCUT_EVERY_MS) {
|
|
16324
|
+
this.#qwLnkAt = now;
|
|
16325
|
+
void ensureQwenworkShortcuts({ log }).catch(() => void 0);
|
|
16326
|
+
}
|
|
16327
|
+
void this.#qwRelaunch?.tick().catch(() => void 0);
|
|
16328
|
+
}, WB_APP_WATCH_MS);
|
|
15658
16329
|
this.#qwAppTimer.unref();
|
|
15659
16330
|
this.#cozeAppTimer = setInterval(() => void this.#cozeApp.tick(), WB_APP_WATCH_MS);
|
|
15660
16331
|
this.#cozeAppTimer.unref();
|
|
@@ -15684,6 +16355,7 @@ var Daemon = class {
|
|
|
15684
16355
|
if (ids.length === 0) return;
|
|
15685
16356
|
void winAppLocator(log).refresh(ids).then(() => {
|
|
15686
16357
|
for (const tool of ["qoder_cn", "qoder"]) if (ids.includes(tool)) void ensureQoderShortcuts(tool, { log }).catch(() => void 0);
|
|
16358
|
+
if (ids.includes("qwenwork")) void ensureQwenworkShortcuts({ log }).catch(() => void 0);
|
|
15687
16359
|
}).catch(() => void 0);
|
|
15688
16360
|
};
|
|
15689
16361
|
locateApps();
|
|
@@ -15763,7 +16435,8 @@ var Daemon = class {
|
|
|
15763
16435
|
onLoadedChange: () => this.#onCodexLoaded(),
|
|
15764
16436
|
// MCP 授权 Codex 不触发 hook:替它报 PermissionRequest,屏上显示待确认(Bug 表 162 / 164)
|
|
15765
16437
|
report: (input) => runCodexHook(this.#deps.nodePath, input),
|
|
15766
|
-
onIdle: (tid) => this.#codexIdle(tid, w)
|
|
16438
|
+
onIdle: (tid) => this.#codexIdle(tid, w),
|
|
16439
|
+
claims: this.#codexClaims
|
|
15767
16440
|
})
|
|
15768
16441
|
);
|
|
15769
16442
|
}
|
|
@@ -16053,6 +16726,9 @@ var Daemon = class {
|
|
|
16053
16726
|
if (this.#dumateTimer) clearInterval(this.#dumateTimer);
|
|
16054
16727
|
if (this.#dmCdpTimer) clearInterval(this.#dmCdpTimer);
|
|
16055
16728
|
if (this.#wbAppTimer) clearInterval(this.#wbAppTimer);
|
|
16729
|
+
if (this.#chatgptAppTimer) clearInterval(this.#chatgptAppTimer);
|
|
16730
|
+
this.#chatgptApp.stop();
|
|
16731
|
+
this.#chatgptCodex.stop();
|
|
16056
16732
|
if (this.#qwAppTimer) clearInterval(this.#qwAppTimer);
|
|
16057
16733
|
this.#qwApp.stop();
|
|
16058
16734
|
if (this.#cozeAppTimer) clearInterval(this.#cozeAppTimer);
|
|
@@ -16999,6 +17675,15 @@ var Daemon = class {
|
|
|
16999
17675
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u5728\u5343\u95EE\u529E\u516C\u91CC\u5EFA\u4EFB\u52A1\u51FA\u9519\uFF1A${e.message}`);
|
|
17000
17676
|
r = { status: "failed", reason: "window_failed", message: e.message };
|
|
17001
17677
|
}
|
|
17678
|
+
if (r.status === "not_sent" && r.reason === APP_NOT_RUNNING) {
|
|
17679
|
+
let missing = false;
|
|
17680
|
+
try {
|
|
17681
|
+
missing = await this.#deps.qwPortMissing();
|
|
17682
|
+
} catch {
|
|
17683
|
+
missing = false;
|
|
17684
|
+
}
|
|
17685
|
+
if (missing) r = { status: "not_sent", reason: APP_PORT_MISSING, message: "\u5343\u95EE\u529E\u516C\u5F00\u7740\uFF0C\u4F46\u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF08\u6216\u8C03\u8BD5\u7AEF\u53E3\u7684\u76D1\u542C\u8005\u4E0D\u5BF9\uFF09\uFF0C\u6CA1\u5EFA\u4EFB\u52A1" };
|
|
17686
|
+
}
|
|
17002
17687
|
if (r.status !== "started") {
|
|
17003
17688
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u6CA1\u80FD\u5728\u5343\u95EE\u529E\u516C\u91CC\u5EFA\u4EFB\u52A1\uFF1A${r.message}`);
|
|
17004
17689
|
if (r.status === "not_sent") this.#report(cmd, "", "rejected", "", r.reason);
|
|
@@ -17072,7 +17757,7 @@ var Daemon = class {
|
|
|
17072
17757
|
const enabled = new Set(loadConfig().enabledTools);
|
|
17073
17758
|
let names = [];
|
|
17074
17759
|
try {
|
|
17075
|
-
names =
|
|
17760
|
+
names = readdirSync12(stateDir());
|
|
17076
17761
|
} catch {
|
|
17077
17762
|
return out;
|
|
17078
17763
|
}
|
|
@@ -19190,7 +19875,7 @@ function runDirect(real2, args, env = process.env) {
|
|
|
19190
19875
|
|
|
19191
19876
|
// src/wrap/codexLaunch.ts
|
|
19192
19877
|
import { execFileSync as execFileSync4, spawn as spawn4 } from "child_process";
|
|
19193
|
-
import { closeSync as closeSync9, mkdirSync as mkdirSync14, openSync as openSync9, readdirSync as
|
|
19878
|
+
import { closeSync as closeSync9, mkdirSync as mkdirSync14, openSync as openSync9, readdirSync as readdirSync13, readFileSync as readFileSync16, rmSync as rmSync14, statSync as statSync15, truncateSync as truncateSync2, writeFileSync as writeFileSync14, renameSync as renameSync10 } from "fs";
|
|
19194
19879
|
import { request as request2 } from "http";
|
|
19195
19880
|
import { createServer as createServer5 } from "net";
|
|
19196
19881
|
import { join as join30 } from "path";
|
|
@@ -19475,7 +20160,7 @@ function removeRecord(rec) {
|
|
|
19475
20160
|
function reapWindows(dir2 = windowsDir2(), alive = pidAlive5, args = freshArgs, kill = (pid) => killServer(pid)) {
|
|
19476
20161
|
let names = [];
|
|
19477
20162
|
try {
|
|
19478
|
-
names =
|
|
20163
|
+
names = readdirSync13(dir2).filter((n) => n.endsWith(".json"));
|
|
19479
20164
|
} catch {
|
|
19480
20165
|
return;
|
|
19481
20166
|
}
|