deskbuddy 0.2.0-beta.20 → 0.2.0-beta.22
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 +291 -45
- package/dist/opencode.mjs +343 -2
- package/hook/hook-debug.log +50 -0
- package/hook/opencode.d.mts +21 -1
- package/hook/opencode.mjs +343 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13345,11 +13345,11 @@ var REVOKE_RETRIES = 3;
|
|
|
13345
13345
|
var REVOKE_RETRY_MS = 50;
|
|
13346
13346
|
var SID_RE3 = /^[A-Za-z0-9_-]{1,128}$/;
|
|
13347
13347
|
var IID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
13348
|
-
function dshMailboxDir(home = deskbuddyHome()) {
|
|
13349
|
-
return join40(home,
|
|
13348
|
+
function dshMailboxDir(home = deskbuddyHome(), channel = "dsh") {
|
|
13349
|
+
return join40(home, channel);
|
|
13350
13350
|
}
|
|
13351
|
-
function sub(home, name) {
|
|
13352
|
-
return join40(dshMailboxDir(home), name);
|
|
13351
|
+
function sub(home, name, channel = "dsh") {
|
|
13352
|
+
return join40(dshMailboxDir(home, channel), name);
|
|
13353
13353
|
}
|
|
13354
13354
|
function ensureDir2(p) {
|
|
13355
13355
|
mkdirSync28(p, { recursive: true, mode: 448 });
|
|
@@ -13372,22 +13372,24 @@ var defaultPidAlive = (pid) => {
|
|
|
13372
13372
|
}
|
|
13373
13373
|
};
|
|
13374
13374
|
function deps(d) {
|
|
13375
|
-
return { home: d.home ?? deskbuddyHome(), now: d.now ?? Date.now, pidAlive: d.pidAlive ?? defaultPidAlive };
|
|
13375
|
+
return { home: d.home ?? deskbuddyHome(), now: d.now ?? Date.now, pidAlive: d.pidAlive ?? defaultPidAlive, channel: d.channel ?? "dsh" };
|
|
13376
13376
|
}
|
|
13377
|
-
function
|
|
13378
|
-
if (!SID_RE3.test(sessionId)) return
|
|
13377
|
+
function liveHeartbeats(sessionId, d0 = {}) {
|
|
13378
|
+
if (!SID_RE3.test(sessionId)) return [];
|
|
13379
13379
|
const d = deps(d0);
|
|
13380
|
+
const dir = sub(d.home, "alive", d.channel);
|
|
13380
13381
|
let names;
|
|
13381
13382
|
try {
|
|
13382
|
-
names = readdirSync11(
|
|
13383
|
+
names = readdirSync11(dir);
|
|
13383
13384
|
} catch {
|
|
13384
|
-
return
|
|
13385
|
+
return [];
|
|
13385
13386
|
}
|
|
13387
|
+
const out = [];
|
|
13386
13388
|
for (const n of names) {
|
|
13387
13389
|
if (!/^\d+\.json$/.test(n)) continue;
|
|
13388
13390
|
let hb;
|
|
13389
13391
|
try {
|
|
13390
|
-
hb = JSON.parse(readFileSync29(join40(
|
|
13392
|
+
hb = JSON.parse(readFileSync29(join40(dir, n), "utf8"));
|
|
13391
13393
|
} catch {
|
|
13392
13394
|
continue;
|
|
13393
13395
|
}
|
|
@@ -13395,23 +13397,27 @@ function dshLiveFor(sessionId, d0 = {}) {
|
|
|
13395
13397
|
if (d.now() - hb.at > DSH_HEARTBEAT_FRESH_MS) continue;
|
|
13396
13398
|
if (!hb.sessions.includes(sessionId)) continue;
|
|
13397
13399
|
if (!d.pidAlive(hb.pid)) continue;
|
|
13398
|
-
|
|
13400
|
+
out.push(hb);
|
|
13399
13401
|
}
|
|
13400
|
-
return
|
|
13402
|
+
return out;
|
|
13403
|
+
}
|
|
13404
|
+
function dshLiveFor(sessionId, d0 = {}) {
|
|
13405
|
+
return liveHeartbeats(sessionId, d0)[0] ?? null;
|
|
13401
13406
|
}
|
|
13402
13407
|
async function dshDeliver(sessionId, msg, d0 = {}) {
|
|
13403
13408
|
const d = deps(d0);
|
|
13404
13409
|
if (!SID_RE3.test(sessionId) || !IID_RE.test(msg.instructionId)) return { ok: false, reason: "bad_id" };
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
const
|
|
13410
|
+
const hb = (d0.pick ?? dshLiveFor)(sessionId, d);
|
|
13411
|
+
if (!hb) return { ok: false, reason: "not_live" };
|
|
13412
|
+
const inbox = sub(d.home, "inbox", d.channel);
|
|
13413
|
+
const ackDir = sub(d.home, "ack", d.channel);
|
|
13408
13414
|
ensureDir2(inbox);
|
|
13409
13415
|
ensureDir2(ackDir);
|
|
13410
13416
|
const letter = join40(inbox, `${sessionId}.${msg.instructionId}.json`);
|
|
13411
13417
|
const ackPath = join40(ackDir, `${msg.instructionId}.json`);
|
|
13412
13418
|
if (existsSync24(letter)) return { ok: false, reason: "inbox_busy" };
|
|
13413
13419
|
const writtenAt = d.now();
|
|
13414
|
-
writeJson(letter, { instructionId: msg.instructionId, text: msg.text, at: writtenAt });
|
|
13420
|
+
writeJson(letter, { instructionId: msg.instructionId, text: msg.text, at: writtenAt, ...d0.addressed ? { pid: hb.pid } : {} });
|
|
13415
13421
|
const sleep3 = d0.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
13416
13422
|
let deadline = writtenAt + (d0.timeoutMs ?? DSH_ACK_TIMEOUT_MS);
|
|
13417
13423
|
let claimed = false;
|
|
@@ -13465,8 +13471,13 @@ async function revoke(letter, sleep3) {
|
|
|
13465
13471
|
}
|
|
13466
13472
|
return existsSync24(letter) ? "failed" : "claimed";
|
|
13467
13473
|
}
|
|
13468
|
-
function dshClearInbox(home = deskbuddyHome()) {
|
|
13469
|
-
|
|
13474
|
+
function dshClearInbox(home = deskbuddyHome(), channel = "dsh") {
|
|
13475
|
+
return clearDir(sub(home, "inbox", channel));
|
|
13476
|
+
}
|
|
13477
|
+
function dshClearAcks(home = deskbuddyHome(), channel = "dsh") {
|
|
13478
|
+
return clearDir(sub(home, "ack", channel));
|
|
13479
|
+
}
|
|
13480
|
+
function clearDir(inbox) {
|
|
13470
13481
|
let names;
|
|
13471
13482
|
try {
|
|
13472
13483
|
names = readdirSync11(inbox);
|
|
@@ -13484,6 +13495,29 @@ function dshClearInbox(home = deskbuddyHome()) {
|
|
|
13484
13495
|
return n;
|
|
13485
13496
|
}
|
|
13486
13497
|
|
|
13498
|
+
// src/core/ocMailbox.ts
|
|
13499
|
+
var OC_CHANNEL = "opencode";
|
|
13500
|
+
function pickOcHeartbeat(hbs, sessionId) {
|
|
13501
|
+
if (hbs.length === 0) return null;
|
|
13502
|
+
const rank = (hb) => {
|
|
13503
|
+
const i = Array.isArray(hb.active) ? hb.active.indexOf(sessionId) : -1;
|
|
13504
|
+
return i < 0 ? Number.MAX_SAFE_INTEGER : i;
|
|
13505
|
+
};
|
|
13506
|
+
return [...hbs].sort((a, b) => rank(a) - rank(b) || b.at - a.at || a.pid - b.pid)[0];
|
|
13507
|
+
}
|
|
13508
|
+
function ocLiveFor(sessionId, d0 = {}) {
|
|
13509
|
+
return pickOcHeartbeat(liveHeartbeats(sessionId, { ...d0, channel: OC_CHANNEL }), sessionId);
|
|
13510
|
+
}
|
|
13511
|
+
function ocDeliver(sessionId, msg, d0 = {}) {
|
|
13512
|
+
return dshDeliver(sessionId, msg, { ...d0, channel: OC_CHANNEL, addressed: true, pick: (sid, d) => ocLiveFor(sid, d) });
|
|
13513
|
+
}
|
|
13514
|
+
function ocClearInbox(home = deskbuddyHome()) {
|
|
13515
|
+
return dshClearInbox(home, OC_CHANNEL);
|
|
13516
|
+
}
|
|
13517
|
+
function ocClearAcks(home = deskbuddyHome()) {
|
|
13518
|
+
return dshClearAcks(home, OC_CHANNEL);
|
|
13519
|
+
}
|
|
13520
|
+
|
|
13487
13521
|
// src/daemon/index.ts
|
|
13488
13522
|
var UPDATING_REASON = "updating";
|
|
13489
13523
|
var AGENT_NOT_LOGGED_IN = "agent_not_logged_in";
|
|
@@ -13652,6 +13686,8 @@ var Daemon = class {
|
|
|
13652
13686
|
qoderAppSync: deps2.qoderAppSync ?? ((job) => syncQoderApp(job)),
|
|
13653
13687
|
dshLive: deps2.dshLive ?? ((sid) => dshLiveFor(sid) != null),
|
|
13654
13688
|
dshDeliver: deps2.dshDeliver ?? ((sid, msg) => dshDeliver(sid, msg)),
|
|
13689
|
+
ocLive: deps2.ocLive ?? ((sid) => ocLiveFor(sid) != null),
|
|
13690
|
+
ocDeliver: deps2.ocDeliver ?? ((sid, msg) => ocDeliver(sid, msg)),
|
|
13655
13691
|
loginGuide: deps2.loginGuide ?? defaultLoginGuide(log, deps2.now ?? (() => Date.now()), () => {
|
|
13656
13692
|
const offer = defaultWorkbuddyOffer(log);
|
|
13657
13693
|
if (offer) void offer.maybeAsk("runtime").catch(() => {
|
|
@@ -14267,6 +14303,18 @@ var Daemon = class {
|
|
|
14267
14303
|
return;
|
|
14268
14304
|
}
|
|
14269
14305
|
}
|
|
14306
|
+
if (cmd.tool === "opencode" && !this.#queue.busy(sid)) {
|
|
14307
|
+
let live = false;
|
|
14308
|
+
try {
|
|
14309
|
+
live = this.#deps.ocLive(sid);
|
|
14310
|
+
} catch {
|
|
14311
|
+
live = false;
|
|
14312
|
+
}
|
|
14313
|
+
if (live) {
|
|
14314
|
+
void this.#track(this.#toOcWindow(cmd, sid));
|
|
14315
|
+
return;
|
|
14316
|
+
}
|
|
14317
|
+
}
|
|
14270
14318
|
}
|
|
14271
14319
|
this.#afterPty(cmd);
|
|
14272
14320
|
}
|
|
@@ -14568,6 +14616,32 @@ var Daemon = class {
|
|
|
14568
14616
|
const gone = ack.reason === "not_live" || ack.reason === "stale";
|
|
14569
14617
|
this.#report(cmd, sid, gone ? "rejected" : "failed", "window", gone ? "session_gone" : "window_failed");
|
|
14570
14618
|
}
|
|
14619
|
+
/**
|
|
14620
|
+
* 经插件信箱往开着这个会话的 opencode 进程里发一句话(spec 2026-10-08)。回报同 dsh:空闲 started / 正忙 queued(mode=window)。
|
|
14621
|
+
* 确定没发出去(not_live:心跳刚失效 / 进程里没这个会话;no_ack:没人认领、信已收回)→ 退回原来的后台续跑。
|
|
14622
|
+
* 🔴 其余失败(认领后没回执、promptAsync 出错 / 超时)不退回:可能已经发出去了,再跑一遍 = 执行两次
|
|
14623
|
+
*/
|
|
14624
|
+
async #toOcWindow(cmd, sid) {
|
|
14625
|
+
let ack;
|
|
14626
|
+
try {
|
|
14627
|
+
ack = await this.#deps.ocDeliver(sid, { instructionId: cmd.instructionId, text: cmd.text });
|
|
14628
|
+
} catch (e) {
|
|
14629
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u6295\u9012\u7ED9 opencode \u51FA\u9519\uFF1A${e.message}`);
|
|
14630
|
+
ack = { ok: false, reason: "deliver_error" };
|
|
14631
|
+
}
|
|
14632
|
+
if (ack.ok) {
|
|
14633
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u5DF2\u7ECF\u63D2\u4EF6\u53D1\u8FDB opencode \u4F1A\u8BDD ${sid.slice(0, 12)}${ack.busy ? "\uFF08\u6B63\u5FD9\uFF0C\u6392\u8FDB\u4E0B\u4E00\u8F6E\uFF09" : ""} len=${cmd.text.length}`);
|
|
14634
|
+
this.#report(cmd, sid, ack.busy ? "queued" : "started", "window");
|
|
14635
|
+
return;
|
|
14636
|
+
}
|
|
14637
|
+
if (ack.reason === "not_live" || ack.reason === "no_ack") {
|
|
14638
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} opencode \u7A97\u53E3\u6CA1\u63A5\uFF08${ack.reason}\uFF09\uFF0C\u6539\u8D70\u540E\u53F0\u7EED\u8DD1`);
|
|
14639
|
+
this.#afterPty(cmd);
|
|
14640
|
+
return;
|
|
14641
|
+
}
|
|
14642
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} \u6CA1\u80FD\u53D1\u8FDB opencode \u4F1A\u8BDD\uFF08${ack.reason ?? "\u672A\u77E5"}\uFF09`);
|
|
14643
|
+
this.#report(cmd, sid, "failed", "window", "window_failed");
|
|
14644
|
+
}
|
|
14571
14645
|
/** 回报一条指令的处理结果。2 秒超时、失败不重试,只记日志(seen 去重命中的不会走到这里) */
|
|
14572
14646
|
#report(cmd, sessionId, status, mode, reason = "") {
|
|
14573
14647
|
const payload = {
|
|
@@ -14991,6 +15065,10 @@ async function runDaemon(deps2 = {}) {
|
|
|
14991
15065
|
try {
|
|
14992
15066
|
const n = dshClearInbox();
|
|
14993
15067
|
if (n > 0) log(`\u6E05\u6389 ${n} \u5C01\u4E0A\u6B21\u9057\u7559\u7684 dsh \u4FE1`);
|
|
15068
|
+
const m = ocClearInbox();
|
|
15069
|
+
if (m > 0) log(`\u6E05\u6389 ${m} \u5C01\u4E0A\u6B21\u9057\u7559\u7684 opencode \u4FE1`);
|
|
15070
|
+
dshClearAcks();
|
|
15071
|
+
ocClearAcks();
|
|
14994
15072
|
} catch {
|
|
14995
15073
|
}
|
|
14996
15074
|
await d.startGate();
|
|
@@ -15879,6 +15957,15 @@ var Injector = class {
|
|
|
15879
15957
|
job.reply(job.finalMsg);
|
|
15880
15958
|
return;
|
|
15881
15959
|
}
|
|
15960
|
+
const unsafe = this.#d.rejectText?.(job.text) ?? null;
|
|
15961
|
+
if (unsafe) {
|
|
15962
|
+
this.#remember(job);
|
|
15963
|
+
job.done = true;
|
|
15964
|
+
this.#d.log?.(`\u4E0D\u5199 ${job.instructionId}\uFF1A${unsafe}`);
|
|
15965
|
+
job.finalMsg = { instructionId: job.instructionId, status: "rejected", mode: "window", reason: unsafe };
|
|
15966
|
+
job.reply(job.finalMsg);
|
|
15967
|
+
return;
|
|
15968
|
+
}
|
|
15882
15969
|
if (!this.#owns(job.sessionId)) {
|
|
15883
15970
|
this.#remember(job);
|
|
15884
15971
|
job.done = true;
|
|
@@ -15910,6 +15997,8 @@ var Injector = class {
|
|
|
15910
15997
|
this.#sawTrust = true;
|
|
15911
15998
|
}
|
|
15912
15999
|
if (v.permission) why.push("permission");
|
|
16000
|
+
if (v.busy) why.push("generating");
|
|
16001
|
+
if (v.dialog) why.push("dialog:" + v.dialog);
|
|
15913
16002
|
const pristine = this.#d.startupIdle === true && !this.#userKeyed && !this.#injectedOnce && !last;
|
|
15914
16003
|
if (v.input === null) why.push("no-input-box");
|
|
15915
16004
|
else if (v.input !== "" && !v.placeholder && !pristine) why.push("user-typing");
|
|
@@ -15980,8 +16069,9 @@ var Injector = class {
|
|
|
15980
16069
|
await this.#d.sleep(PASTE_SETTLE_MS);
|
|
15981
16070
|
if (job.done) return;
|
|
15982
16071
|
const v = this.#d.view();
|
|
15983
|
-
|
|
15984
|
-
|
|
16072
|
+
const blocked = !!v && (!!v.dialog || v.busy === true || v.trust === true);
|
|
16073
|
+
if (!v || blocked || !boxMatches(v, body)) {
|
|
16074
|
+
this.#d.log?.(blocked ? `\u7C98\u8D34\u540E\u5C4F\u5E55\u4E0D\u53EF\u5199\uFF08${v?.dialog ? "dialog:" + v.dialog : v?.busy ? "generating" : "trust-dialog"}\uFF09\uFF0C\u653E\u5F03\u8FD9\u6761` : "\u7C98\u8D34\u540E\u8F93\u5165\u6846\u5BF9\u4E0D\u4E0A\uFF0C\u653E\u5F03\u8FD9\u6761");
|
|
15985
16075
|
this.#final(job, { status: "failed", mode: "window", reason: "window_failed" });
|
|
15986
16076
|
return;
|
|
15987
16077
|
}
|
|
@@ -16174,6 +16264,61 @@ function runDirect(real2, args, env = process.env) {
|
|
|
16174
16264
|
});
|
|
16175
16265
|
}
|
|
16176
16266
|
|
|
16267
|
+
// src/wrap/screenRules.ts
|
|
16268
|
+
var Q_RULE_RE = /^─{20,}$/;
|
|
16269
|
+
var Q_TRUST_RE = /Do you trust the files in this folder\?/;
|
|
16270
|
+
var Q_PERMISSION_RE = /Permission Required|Apply this change\?|\d\. Allow once|\d\. Allow for this session/;
|
|
16271
|
+
var Q_LOGIN_RE = /Sign in to continue|Login with QoderCN|How would you like to authenticate/;
|
|
16272
|
+
var Q_BUSY_RE = /\(esc to cancel\b/;
|
|
16273
|
+
var Q_PLACEHOLDER_RE = /^Type your message or @\S*$/;
|
|
16274
|
+
var Q_FOOTER_RE = /· ctx\b/;
|
|
16275
|
+
function bottomRegion(lines3, rules) {
|
|
16276
|
+
const anchor = rules.length >= 3 ? rules[rules.length - 3] : rules[rules.length - 1];
|
|
16277
|
+
return lines3.slice(Math.max(0, anchor - 1)).join("\n");
|
|
16278
|
+
}
|
|
16279
|
+
function qoderCnRule(lines3) {
|
|
16280
|
+
const rules = [];
|
|
16281
|
+
lines3.forEach((l, i) => {
|
|
16282
|
+
if (Q_RULE_RE.test(l.trim())) rules.push(i);
|
|
16283
|
+
});
|
|
16284
|
+
if (rules.length === 0) return { permission: false, input: null };
|
|
16285
|
+
const text = bottomRegion(lines3, rules);
|
|
16286
|
+
if (Q_TRUST_RE.test(text)) return { permission: false, input: null, trust: true };
|
|
16287
|
+
if (Q_LOGIN_RE.test(text)) return { permission: false, input: null, dialog: "login" };
|
|
16288
|
+
const permission = Q_PERMISSION_RE.test(text);
|
|
16289
|
+
let input = null;
|
|
16290
|
+
if (rules.length >= 2) {
|
|
16291
|
+
const a = rules[rules.length - 2];
|
|
16292
|
+
const b = rules[rules.length - 1];
|
|
16293
|
+
const box = lines3.slice(a + 1, b).map((l) => l.trimEnd());
|
|
16294
|
+
if (box.length > 0 && /^\s*>/.test(box[0])) input = box.join("\n").replace(/^\s*>\s?/, "").trim();
|
|
16295
|
+
}
|
|
16296
|
+
const v = { permission, input };
|
|
16297
|
+
if (Q_BUSY_RE.test(text)) v.busy = true;
|
|
16298
|
+
if (input !== null && Q_PLACEHOLDER_RE.test(input)) v.placeholder = true;
|
|
16299
|
+
else if (input !== null && input.startsWith("/")) v.dialog = "slash-menu";
|
|
16300
|
+
if (!v.dialog && lines3.slice(rules[rules.length - 1] + 1).some((l) => l.trim() !== "" && !Q_FOOTER_RE.test(l))) v.dialog = "popup";
|
|
16301
|
+
return v;
|
|
16302
|
+
}
|
|
16303
|
+
function textRejectFor(tool) {
|
|
16304
|
+
if (tool !== "qoder_cn") return void 0;
|
|
16305
|
+
return (text) => /^[/!]/.test(oneLine(text)) ? "slash_command" : null;
|
|
16306
|
+
}
|
|
16307
|
+
var RULES = {
|
|
16308
|
+
claude_code: inspectLines,
|
|
16309
|
+
qoder_cn: qoderCnRule
|
|
16310
|
+
};
|
|
16311
|
+
function screenRuleFor(tool) {
|
|
16312
|
+
return RULES[tool] ?? (() => null);
|
|
16313
|
+
}
|
|
16314
|
+
var INJECT_READY = {
|
|
16315
|
+
claude_code: true,
|
|
16316
|
+
qoder_cn: true
|
|
16317
|
+
};
|
|
16318
|
+
function injectReady(tool) {
|
|
16319
|
+
return INJECT_READY[tool] === true;
|
|
16320
|
+
}
|
|
16321
|
+
|
|
16177
16322
|
// src/wrap/codexLaunch.ts
|
|
16178
16323
|
import { execFileSync as execFileSync16, spawn as spawn8 } from "child_process";
|
|
16179
16324
|
import { closeSync as closeSync10, mkdirSync as mkdirSync30, openSync as openSync10, readdirSync as readdirSync12, readFileSync as readFileSync30, rmSync as rmSync23, statSync as statSync19, truncateSync as truncateSync2, writeFileSync as writeFileSync29, renameSync as renameSync20 } from "fs";
|
|
@@ -16540,9 +16685,62 @@ function workbuddyNotifyArgs(hookPath, platform = process.platform) {
|
|
|
16540
16685
|
return ["--settings", JSON.stringify(settings)];
|
|
16541
16686
|
}
|
|
16542
16687
|
function launchArgs(tool, platform, hookPath = installedHookPath(), sessionId = "") {
|
|
16543
|
-
|
|
16544
|
-
if (tool === "
|
|
16545
|
-
return
|
|
16688
|
+
const sid = sessionId && REGISTER_TOOLS.has(tool) ? ["--session-id", sessionId] : [];
|
|
16689
|
+
if (tool === "workbuddy") return [...workbuddyNotifyArgs(hookPath, platform), ...sid];
|
|
16690
|
+
return sid;
|
|
16691
|
+
}
|
|
16692
|
+
var REGISTER_TOOLS = /* @__PURE__ */ new Set(["qoder_cn", "workbuddy"]);
|
|
16693
|
+
var EARLY_POLL_MS = 3e3;
|
|
16694
|
+
var EARLY_POLL_MAX_MS = 2 * 60 * 1e3;
|
|
16695
|
+
function startEarlyRegister(tool, sid, cwd, text, d) {
|
|
16696
|
+
if (!d.register || !d.transcriptReady) return { stop: async () => {
|
|
16697
|
+
} };
|
|
16698
|
+
const register = d.register;
|
|
16699
|
+
const ready = d.transcriptReady;
|
|
16700
|
+
const every = d.pollMs ?? EARLY_POLL_MS;
|
|
16701
|
+
const deadline = d.now() + (d.pollMaxMs ?? EARLY_POLL_MAX_MS);
|
|
16702
|
+
let stopped = false;
|
|
16703
|
+
let timer = null;
|
|
16704
|
+
let inflight = null;
|
|
16705
|
+
const tick = async () => {
|
|
16706
|
+
timer = null;
|
|
16707
|
+
if (stopped) return;
|
|
16708
|
+
let ok = false;
|
|
16709
|
+
try {
|
|
16710
|
+
ok = await ready(tool, sid);
|
|
16711
|
+
} catch {
|
|
16712
|
+
ok = false;
|
|
16713
|
+
}
|
|
16714
|
+
if (stopped) return;
|
|
16715
|
+
if (ok) {
|
|
16716
|
+
try {
|
|
16717
|
+
await register(tool, sid, cwd, text, false);
|
|
16718
|
+
} catch (e) {
|
|
16719
|
+
d.log(`\u63D0\u524D\u767B\u8BB0\u51FA\u9519\uFF1A${e.message}`);
|
|
16720
|
+
}
|
|
16721
|
+
return;
|
|
16722
|
+
}
|
|
16723
|
+
if (d.now() >= deadline) {
|
|
16724
|
+
d.log(`[${sid.slice(0, 8)}] ${Math.round((d.pollMaxMs ?? EARLY_POLL_MAX_MS) / 1e3)} \u79D2\u5185 jsonl \u6CA1\u6709\u7528\u6237\u6D88\u606F\uFF0C\u7B49\u7A97\u53E3\u9000\u51FA\u65F6\u518D\u767B\u8BB0`);
|
|
16725
|
+
return;
|
|
16726
|
+
}
|
|
16727
|
+
schedule();
|
|
16728
|
+
};
|
|
16729
|
+
const schedule = () => {
|
|
16730
|
+
timer = setTimeout(() => {
|
|
16731
|
+
inflight = tick();
|
|
16732
|
+
}, every);
|
|
16733
|
+
timer.unref?.();
|
|
16734
|
+
};
|
|
16735
|
+
schedule();
|
|
16736
|
+
return {
|
|
16737
|
+
stop: async () => {
|
|
16738
|
+
stopped = true;
|
|
16739
|
+
if (timer) clearTimeout(timer);
|
|
16740
|
+
if (inflight) await inflight.catch(() => {
|
|
16741
|
+
});
|
|
16742
|
+
}
|
|
16743
|
+
};
|
|
16546
16744
|
}
|
|
16547
16745
|
async function runPromptLaunch(tool, windowId, cwd, d) {
|
|
16548
16746
|
let real2 = null;
|
|
@@ -16576,17 +16774,21 @@ async function runPromptLaunch(tool, windowId, cwd, d) {
|
|
|
16576
16774
|
d.log(`[${windowId.slice(0, 8)}] \u6CA1\u7B49\u5230\u9996\u53E5\uFF0C\u8D77\u4E00\u4E2A\u7A7A\u7684 ${tool}`);
|
|
16577
16775
|
}
|
|
16578
16776
|
const answerWindowId = tool === "workbuddy" || tool === "codex" ? randomBytes8(16).toString("hex") : "";
|
|
16579
|
-
|
|
16580
|
-
|
|
16777
|
+
const sid = REGISTER_TOOLS.has(tool) ? (d.newSessionId ?? randomUUID4)() : "";
|
|
16778
|
+
const env = tool === "qoder_cn" ? { ...promptEnv(tool, d.env), ...real2.env ?? {} } : promptEnv(tool, d.env, process.pid, answerWindowId);
|
|
16779
|
+
const args = [...launchArgs(tool, d.platform, void 0, sid), ...promptArgs(text)];
|
|
16780
|
+
const early = sid ? startEarlyRegister(tool, sid, cwd, text, d) : null;
|
|
16781
|
+
let code;
|
|
16782
|
+
try {
|
|
16783
|
+
code = tool === "qoder_cn" ? await d.run(tool, real2, args, env) : await d.run(tool, real2, args, env, answerWindowId);
|
|
16784
|
+
} finally {
|
|
16785
|
+
await early?.stop();
|
|
16581
16786
|
}
|
|
16582
|
-
|
|
16583
|
-
const env = { ...promptEnv(tool, d.env), ...real2.env ?? {} };
|
|
16584
|
-
const code = await d.run(tool, real2, [...launchArgs(tool, d.platform, "", sid), ...promptArgs(text)], env);
|
|
16585
|
-
if (d.afterQoder) {
|
|
16787
|
+
if (sid && d.register) {
|
|
16586
16788
|
try {
|
|
16587
|
-
await d.
|
|
16789
|
+
await d.register(tool, sid, cwd, text, true);
|
|
16588
16790
|
} catch (e) {
|
|
16589
|
-
d.log(`\u767B\u8BB0
|
|
16791
|
+
d.log(`\u767B\u8BB0\u4F1A\u8BDD\u51FA\u9519\uFF1A${e.message}`);
|
|
16590
16792
|
}
|
|
16591
16793
|
}
|
|
16592
16794
|
return code;
|
|
@@ -16950,19 +17152,47 @@ async function runAnswerWindow(tool, real2, args, env, windowId, d) {
|
|
|
16950
17152
|
});
|
|
16951
17153
|
}
|
|
16952
17154
|
|
|
17155
|
+
// src/wrap/launchRegister.ts
|
|
17156
|
+
import { dirname as dirname26 } from "path";
|
|
17157
|
+
async function launchTranscriptReady(tool, sessionId) {
|
|
17158
|
+
try {
|
|
17159
|
+
if (tool === "qoder_cn") {
|
|
17160
|
+
const p = await findTranscript(qoderConfigDir(tool), sessionId);
|
|
17161
|
+
return !!p && (await transcriptInfo(p)).title !== "";
|
|
17162
|
+
}
|
|
17163
|
+
if (tool === "workbuddy") {
|
|
17164
|
+
const p = findWbTranscript(dirname26(workbuddySettingsPath()), sessionId);
|
|
17165
|
+
return !!p && wbTranscriptInfo(p).title !== "";
|
|
17166
|
+
}
|
|
17167
|
+
return false;
|
|
17168
|
+
} catch {
|
|
17169
|
+
return false;
|
|
17170
|
+
}
|
|
17171
|
+
}
|
|
17172
|
+
var defaultDeps = { qoder: (j) => syncQoderApp(j), workbuddy: (j) => registerWorkbuddySession(j) };
|
|
17173
|
+
async function launchRegister(tool, sessionId, cwd, firstLine, final, d = defaultDeps) {
|
|
17174
|
+
if (tool === "workbuddy") return d.workbuddy({ sessionId, cwd, fallbackTitle: firstLine ?? "" });
|
|
17175
|
+
if (tool !== "qoder_cn") return "skip tool";
|
|
17176
|
+
const r = await d.qoder({ tool, kind: "register", sessionId, cwd, fallbackTitle: firstLine ?? "" });
|
|
17177
|
+
if (!final) return r;
|
|
17178
|
+
const c = await d.qoder({ tool, kind: "clear", sessionId, cwd });
|
|
17179
|
+
return `${r}\uFF1B\u6E05\u7F13\u5B58 ${c}`;
|
|
17180
|
+
}
|
|
17181
|
+
|
|
16953
17182
|
// src/wrap/run.ts
|
|
16954
17183
|
var DEFAULT_TTL_MS = 10 * 60 * 1e3;
|
|
16955
17184
|
var TICK_MS = 300;
|
|
16956
17185
|
var MAX_LOG_BYTES3 = 512 * 1024;
|
|
16957
17186
|
var WRAP_HINT = "DeskBuddy \u5305\u88C5\u51FA\u9519\uFF0C\u8BF7\u8BBE DESKBUDDY_WRAP_OFF=1 \u4E34\u65F6\u7ED5\u5F00\uFF08\u4F8B\u5982 DESKBUDDY_WRAP_OFF=1 claude\uFF09";
|
|
16958
17187
|
var SESSION_ENV = ["DESKBUDDY_SHIM", "CLAUDECODE", "CLAUDE_CODE_CHILD_SESSION", "CLAUDE_CODE_SESSION_ID", "CLAUDE_PID", "CLAUDE_CODE_ENTRYPOINT"];
|
|
16959
|
-
function childEnv(src, windowId) {
|
|
17188
|
+
function childEnv(src, windowId, takeover = true) {
|
|
16960
17189
|
const env = {};
|
|
16961
17190
|
for (const [k, v] of Object.entries(src)) {
|
|
16962
17191
|
if (SESSION_ENV.includes(k)) continue;
|
|
16963
17192
|
env[k] = v;
|
|
16964
17193
|
}
|
|
16965
|
-
env.DESKBUDDY_WINDOW = windowId;
|
|
17194
|
+
if (takeover) env.DESKBUDDY_WINDOW = windowId;
|
|
17195
|
+
else delete env.DESKBUDDY_WINDOW;
|
|
16966
17196
|
env.DESKBUDDY_WRAPPED = "1";
|
|
16967
17197
|
return scrubAgentEnv(env);
|
|
16968
17198
|
}
|
|
@@ -17107,16 +17337,30 @@ function defaultPromptDeps(deps2, cwd, launchId) {
|
|
|
17107
17337
|
})
|
|
17108
17338
|
);
|
|
17109
17339
|
}
|
|
17340
|
+
if (tool === "qoder_cn") return runQoderCnWindow(real2, args, env, cwd, deps2);
|
|
17110
17341
|
return runDirect(real2, args, env);
|
|
17111
17342
|
},
|
|
17112
17343
|
env: deps2.env,
|
|
17113
17344
|
log: deps2.log,
|
|
17114
|
-
|
|
17115
|
-
const r = await
|
|
17116
|
-
deps2.log(`[${(launchId ?? "").slice(0, 8)}]
|
|
17117
|
-
}
|
|
17345
|
+
register: async (tool, sid, c, first, final) => {
|
|
17346
|
+
const r = await launchRegister(tool, sid, c, first, final === true);
|
|
17347
|
+
deps2.log(`[${(launchId ?? "").slice(0, 8)}] ${tool} \u4F1A\u8BDD ${sid.slice(0, 8)} \u767B\u8BB0\u8FDB\u684C\u9762\u7AEF\u5217\u8868${final ? "\uFF08\u7A97\u53E3\u9000\u51FA\uFF09" : "\uFF08\u63D0\u524D\uFF09"}\uFF1A${r}`);
|
|
17348
|
+
},
|
|
17349
|
+
transcriptReady: (tool, sid) => launchTranscriptReady(tool, sid)
|
|
17118
17350
|
};
|
|
17119
17351
|
}
|
|
17352
|
+
async function runQoderCnWindow(real2, args, env, cwd, deps2, impl = {}) {
|
|
17353
|
+
const direct = impl.direct ?? runDirect;
|
|
17354
|
+
if (deps2.platform === "win32") return direct(real2, args, env);
|
|
17355
|
+
let r = null;
|
|
17356
|
+
try {
|
|
17357
|
+
r = await (impl.wrap ?? wrapInPty)("qoder_cn", real2, args, { ...deps2, env, cwd: () => cwd }, { launched: false, takeover: impl.takeover ?? injectReady("qoder_cn") });
|
|
17358
|
+
} catch (e) {
|
|
17359
|
+
deps2.log(`Qoder CN \u7A97\u53E3\u5305\u88C5\u5931\u8D25\uFF0C\u76F4\u63A5\u8FD0\u884C\uFF1A${e.message}`);
|
|
17360
|
+
r = null;
|
|
17361
|
+
}
|
|
17362
|
+
return r !== null ? r : direct(real2, args, env);
|
|
17363
|
+
}
|
|
17120
17364
|
async function runLaunch(argv, deps2 = defaultWrapDeps(), ld = defaultLaunchDeps()) {
|
|
17121
17365
|
const runId = argv[0] ?? "";
|
|
17122
17366
|
const hi = argv.indexOf("--home");
|
|
@@ -17184,15 +17428,15 @@ async function wrapInPty(tool, real2, args, deps2, opts = {}) {
|
|
|
17184
17428
|
cols,
|
|
17185
17429
|
rows,
|
|
17186
17430
|
cwd: deps2.cwd(),
|
|
17187
|
-
env: childEnv(deps2.env, windowId)
|
|
17431
|
+
env: childEnv(deps2.env, windowId, opts.takeover !== false)
|
|
17188
17432
|
});
|
|
17189
17433
|
} catch (e) {
|
|
17190
17434
|
deps2.log(`pty \u8D77\u4E0D\u6765\uFF0C\u653E\u884C\u76F4\u8DD1\uFF1A${e.message}`);
|
|
17191
17435
|
return null;
|
|
17192
17436
|
}
|
|
17193
|
-
return runAttached(tool, p, term, windowId, deps2, opts.launched === true);
|
|
17437
|
+
return runAttached(tool, p, term, windowId, deps2, opts.launched === true, opts.takeover !== false);
|
|
17194
17438
|
}
|
|
17195
|
-
function runAttached(tool, p, term0, windowId, deps2, launched = false) {
|
|
17439
|
+
function runAttached(tool, p, term0, windowId, deps2, launched = false, takeover = true) {
|
|
17196
17440
|
const { stdin, stdout: stdout7 } = deps2;
|
|
17197
17441
|
let term = term0;
|
|
17198
17442
|
let finished = false;
|
|
@@ -17222,13 +17466,15 @@ function runAttached(tool, p, term0, windowId, deps2, launched = false) {
|
|
|
17222
17466
|
};
|
|
17223
17467
|
const injector = new Injector({
|
|
17224
17468
|
now: () => Date.now(),
|
|
17225
|
-
|
|
17469
|
+
// 按工具取读屏规则(screenRules.ts);判断不了返回 null = 不可写
|
|
17470
|
+
view: () => term ? screenRuleFor(tool)(screenLines(term), screenLinesUndimmed(term)) : null,
|
|
17226
17471
|
lastEvent: () => readLastEvent(windowId),
|
|
17227
17472
|
write: toPty,
|
|
17228
17473
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
17229
17474
|
holdKeys,
|
|
17230
17475
|
log: (m) => deps2.log(`[${windowId.slice(0, 8)}] ${m}`),
|
|
17231
|
-
startupIdle: launched
|
|
17476
|
+
startupIdle: launched,
|
|
17477
|
+
rejectText: textRejectFor(tool)
|
|
17232
17478
|
});
|
|
17233
17479
|
p.onData((d) => {
|
|
17234
17480
|
try {
|
|
@@ -17296,7 +17542,7 @@ function runAttached(tool, p, term0, windowId, deps2, launched = false) {
|
|
|
17296
17542
|
const trustCtx = trust ? trustContext(tool, trust.forms, deps2.cwd()) : void 0;
|
|
17297
17543
|
let answerChain = Promise.resolve();
|
|
17298
17544
|
const secret = randomBytes10(32);
|
|
17299
|
-
try {
|
|
17545
|
+
if (takeover) try {
|
|
17300
17546
|
writeWindow(windowId, {
|
|
17301
17547
|
pid: process.pid,
|
|
17302
17548
|
childPid: p.pid,
|
|
@@ -17310,7 +17556,7 @@ function runAttached(tool, p, term0, windowId, deps2, launched = false) {
|
|
|
17310
17556
|
} catch (e) {
|
|
17311
17557
|
deps2.log(`\u767B\u8BB0\u7A97\u53E3\u5931\u8D25\uFF1A${e.message}`);
|
|
17312
17558
|
}
|
|
17313
|
-
const channelOk = deps2.platform === "win32" || ensureChannelDir(sock);
|
|
17559
|
+
const channelOk = takeover && (deps2.platform === "win32" || ensureChannelDir(sock));
|
|
17314
17560
|
if (channelOk) {
|
|
17315
17561
|
void startChannelServer(
|
|
17316
17562
|
sock,
|
|
@@ -17357,7 +17603,7 @@ function runAttached(tool, p, term0, windowId, deps2, launched = false) {
|
|
|
17357
17603
|
if (!s2) deps2.log("\u901A\u9053\u8D77\u4E0D\u6765\uFF0C\u8FD9\u4E2A\u7A97\u53E3\u6536\u4E0D\u5230\u8BBE\u5907\u6307\u4EE4");
|
|
17358
17604
|
});
|
|
17359
17605
|
}
|
|
17360
|
-
timer = setInterval(() => {
|
|
17606
|
+
if (takeover) timer = setInterval(() => {
|
|
17361
17607
|
injector.tick().catch(() => {
|
|
17362
17608
|
});
|
|
17363
17609
|
if (trust) {
|