deskbuddy 0.2.0-beta.46 → 0.2.0-beta.47
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/{chunk-Y6D6FMMF.js → chunk-JPCZVBK6.js} +61 -38
- package/dist/cli.js +435 -36
- package/dist/{enable-XTCQ6IFF.js → enable-OICEMNUF.js} +1 -1
- package/dist/ocTranslate.mjs +18 -2
- package/dist/opencode.mjs +48 -5
- package/hook/hook-debug.log +12 -0
- package/hook/ocTranslate.d.mts +3 -0
- package/hook/ocTranslate.mjs +18 -2
- package/hook/opencode.d.mts +12 -1
- package/hook/opencode.mjs +48 -5
- package/package.json +1 -1
|
@@ -203,7 +203,10 @@ function loadConfig() {
|
|
|
203
203
|
cozeApp: typeof parsed.cozeApp === "boolean" ? parsed.cozeApp : void 0,
|
|
204
204
|
cozeCdpPort: portOrUndefined(parsed.cozeCdpPort),
|
|
205
205
|
chatgptAppPort: typeof parsed.chatgptAppPort === "boolean" ? parsed.chatgptAppPort : void 0,
|
|
206
|
-
chatgptCdpPort: portOrUndefined(parsed.chatgptCdpPort)
|
|
206
|
+
chatgptCdpPort: portOrUndefined(parsed.chatgptCdpPort),
|
|
207
|
+
focusSessionOnRemote: typeof parsed.focusSessionOnRemote === "boolean" ? parsed.focusSessionOnRemote : void 0,
|
|
208
|
+
focusSessionOnRemoteWindows: typeof parsed.focusSessionOnRemoteWindows === "boolean" ? parsed.focusSessionOnRemoteWindows : void 0,
|
|
209
|
+
focusSessionInApp: typeof parsed.focusSessionInApp === "boolean" ? parsed.focusSessionInApp : void 0
|
|
207
210
|
};
|
|
208
211
|
} catch {
|
|
209
212
|
return fallback;
|
|
@@ -6338,6 +6341,52 @@ var WsCdpClient = class {
|
|
|
6338
6341
|
|
|
6339
6342
|
// src/daemon/workbuddyApp/keeper.ts
|
|
6340
6343
|
import { posix as posix2, win32 as win326 } from "path";
|
|
6344
|
+
|
|
6345
|
+
// src/daemon/userIdle.ts
|
|
6346
|
+
function parseHidIdleMs(out) {
|
|
6347
|
+
const m = /"HIDIdleTime"\s*=\s*(\d+)/.exec(out);
|
|
6348
|
+
return m ? Math.floor(Number(m[1]) / 1e6) : null;
|
|
6349
|
+
}
|
|
6350
|
+
var WIN_PRESENCE_PS = String.raw`Add-Type @"
|
|
6351
|
+
using System; using System.Runtime.InteropServices;
|
|
6352
|
+
public class DbUserIdle {
|
|
6353
|
+
[StructLayout(LayoutKind.Sequential)] public struct LII { public uint cbSize; public uint dwTime; }
|
|
6354
|
+
[DllImport("user32.dll")] public static extern bool GetLastInputInfo(ref LII p);
|
|
6355
|
+
[DllImport("kernel32.dll")] public static extern uint GetTickCount();
|
|
6356
|
+
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
|
6357
|
+
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
6358
|
+
}
|
|
6359
|
+
"@
|
|
6360
|
+
$l = New-Object DbUserIdle+LII; $l.cbSize = 8
|
|
6361
|
+
if (-not [DbUserIdle]::GetLastInputInfo([ref]$l)) { exit 1 }
|
|
6362
|
+
$idle = (([uint64][DbUserIdle]::GetTickCount()) - [uint64]$l.dwTime + 4294967296) % 4294967296
|
|
6363
|
+
$fp = [uint32]0; [void][DbUserIdle]::GetWindowThreadProcessId([DbUserIdle]::GetForegroundWindow(), [ref]$fp)
|
|
6364
|
+
$n = ''; try { $n = (Get-Process -Id $fp -ErrorAction Stop).ProcessName } catch {}
|
|
6365
|
+
Write-Output ("{0}|{1}" -f $idle, $n)`;
|
|
6366
|
+
function parseWinPresence(code, stdout2) {
|
|
6367
|
+
const m = /^(\d+)\|(.*)$/m.exec(stdout2.trim());
|
|
6368
|
+
if (code !== 0 || !m) return null;
|
|
6369
|
+
return { idleMs: Number(m[1]), front: m[2].trim() };
|
|
6370
|
+
}
|
|
6371
|
+
async function macIdleMs(exec) {
|
|
6372
|
+
const r = await exec("ioreg", ["-c", "IOHIDSystem", "-d", "4"]);
|
|
6373
|
+
return r.code === 0 ? parseHidIdleMs(r.stdout) : null;
|
|
6374
|
+
}
|
|
6375
|
+
async function winPresence(exec) {
|
|
6376
|
+
const r = await exec("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", WIN_PRESENCE_PS], 15e3);
|
|
6377
|
+
return parseWinPresence(r.code, r.stdout);
|
|
6378
|
+
}
|
|
6379
|
+
async function userIdleMs(exec, platform = process.platform) {
|
|
6380
|
+
try {
|
|
6381
|
+
if (platform === "darwin") return await macIdleMs(exec);
|
|
6382
|
+
if (platform === "win32") return (await winPresence(exec))?.idleMs ?? null;
|
|
6383
|
+
} catch {
|
|
6384
|
+
return null;
|
|
6385
|
+
}
|
|
6386
|
+
return null;
|
|
6387
|
+
}
|
|
6388
|
+
|
|
6389
|
+
// src/daemon/workbuddyApp/keeper.ts
|
|
6341
6390
|
var DEFAULT_CDP_PORT = 39217;
|
|
6342
6391
|
var ENV_NAME = "WORKBUDDY_REMOTE_DEBUGGING_PORT";
|
|
6343
6392
|
var RESTART_GAP_MS = 30 * 60 * 1e3;
|
|
@@ -6418,26 +6467,6 @@ var filePersist = {
|
|
|
6418
6467
|
}
|
|
6419
6468
|
}
|
|
6420
6469
|
};
|
|
6421
|
-
function parseHidIdleMs(out) {
|
|
6422
|
-
const m = /"HIDIdleTime"\s*=\s*(\d+)/.exec(out);
|
|
6423
|
-
return m ? Math.floor(Number(m[1]) / 1e6) : null;
|
|
6424
|
-
}
|
|
6425
|
-
var WIN_PRESENCE_PS = String.raw`Add-Type @"
|
|
6426
|
-
using System; using System.Runtime.InteropServices;
|
|
6427
|
-
public class DbUserIdle {
|
|
6428
|
-
[StructLayout(LayoutKind.Sequential)] public struct LII { public uint cbSize; public uint dwTime; }
|
|
6429
|
-
[DllImport("user32.dll")] public static extern bool GetLastInputInfo(ref LII p);
|
|
6430
|
-
[DllImport("kernel32.dll")] public static extern uint GetTickCount();
|
|
6431
|
-
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
|
6432
|
-
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
6433
|
-
}
|
|
6434
|
-
"@
|
|
6435
|
-
$l = New-Object DbUserIdle+LII; $l.cbSize = 8
|
|
6436
|
-
if (-not [DbUserIdle]::GetLastInputInfo([ref]$l)) { exit 1 }
|
|
6437
|
-
$idle = (([uint64][DbUserIdle]::GetTickCount()) - [uint64]$l.dwTime + 4294967296) % 4294967296
|
|
6438
|
-
$fp = [uint32]0; [void][DbUserIdle]::GetWindowThreadProcessId([DbUserIdle]::GetForegroundWindow(), [ref]$fp)
|
|
6439
|
-
$n = ''; try { $n = (Get-Process -Id $fp -ErrorAction Stop).ProcessName } catch {}
|
|
6440
|
-
Write-Output ("{0}|{1}" -f $idle, $n)`;
|
|
6441
6470
|
var WAKE_LAUNCH_GAP_MS = 60 * 1e3;
|
|
6442
6471
|
var APP_PATH_TTL_MS = 5 * 60 * 1e3;
|
|
6443
6472
|
function defaultWorkbuddyAppPath() {
|
|
@@ -6692,7 +6721,7 @@ var CdpKeeper = class {
|
|
|
6692
6721
|
if (this.#d.userAway) return await this.#d.userAway();
|
|
6693
6722
|
const exec = this.#exec();
|
|
6694
6723
|
if (this.#platform() === "darwin") {
|
|
6695
|
-
const idle =
|
|
6724
|
+
const idle = await macIdleMs(exec);
|
|
6696
6725
|
if (idle === null || idle < AWAY_IDLE_MS) return false;
|
|
6697
6726
|
const asn = (await exec("lsappinfo", ["front"])).stdout.trim();
|
|
6698
6727
|
if (!asn) return false;
|
|
@@ -6700,10 +6729,9 @@ var CdpKeeper = class {
|
|
|
6700
6729
|
return !!name.trim() && !/WorkBuddy/i.test(name);
|
|
6701
6730
|
}
|
|
6702
6731
|
if (this.#platform() === "win32") {
|
|
6703
|
-
const
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
return Number(m[1]) >= AWAY_IDLE_MS && !/^WorkBuddy$/i.test(m[2].trim());
|
|
6732
|
+
const p = await winPresence(exec);
|
|
6733
|
+
if (!p) return false;
|
|
6734
|
+
return p.idleMs >= AWAY_IDLE_MS && !/^WorkBuddy$/i.test(p.front);
|
|
6707
6735
|
}
|
|
6708
6736
|
} catch {
|
|
6709
6737
|
return false;
|
|
@@ -9023,10 +9051,6 @@ function hasMainProc(psOut, brand) {
|
|
|
9023
9051
|
}
|
|
9024
9052
|
return false;
|
|
9025
9053
|
}
|
|
9026
|
-
function parseHidIdleMs2(out) {
|
|
9027
|
-
const m = /"HIDIdleTime"\s*=\s*(\d+)/.exec(out);
|
|
9028
|
-
return m ? Math.floor(Number(m[1]) / 1e6) : null;
|
|
9029
|
-
}
|
|
9030
9054
|
function parseLsappBundle(out) {
|
|
9031
9055
|
const m = /"CFBundleIdentifier"\s*=\s*"([^"]+)"/.exec(out);
|
|
9032
9056
|
return m ? m[1] : null;
|
|
@@ -9307,10 +9331,9 @@ var QoderCdpKeeper = class {
|
|
|
9307
9331
|
async userAway() {
|
|
9308
9332
|
if (this.#isWin() && !this.#d.frontBundle && !this.#d.idleMs) {
|
|
9309
9333
|
try {
|
|
9310
|
-
const
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
return Number(m[1]) >= USER_IDLE_MS && !!m[2].trim() && !this.#d.brand.win.fgName.test(m[2].trim());
|
|
9334
|
+
const p = await winPresence(this.#exec());
|
|
9335
|
+
if (!p) return false;
|
|
9336
|
+
return p.idleMs >= USER_IDLE_MS && !!p.front && !this.#d.brand.win.fgName.test(p.front);
|
|
9314
9337
|
} catch {
|
|
9315
9338
|
return false;
|
|
9316
9339
|
}
|
|
@@ -9333,8 +9356,7 @@ var QoderCdpKeeper = class {
|
|
|
9333
9356
|
return parseLsappBundle((await exec("lsappinfo", ["info", "-only", "bundleid", asn])).stdout);
|
|
9334
9357
|
}
|
|
9335
9358
|
async #defaultIdle() {
|
|
9336
|
-
|
|
9337
|
-
return r.code === 0 ? parseHidIdleMs2(r.stdout) : null;
|
|
9359
|
+
return macIdleMs(this.#exec());
|
|
9338
9360
|
}
|
|
9339
9361
|
/** 主进程(macOS;只认唯一一个;没有 / 认不出 / 多个 → null) */
|
|
9340
9362
|
async #brandMain() {
|
|
@@ -11541,7 +11563,7 @@ var defaultLoginOpenDeps = () => ({
|
|
|
11541
11563
|
});
|
|
11542
11564
|
async function installCodebuddyHooks(log) {
|
|
11543
11565
|
try {
|
|
11544
|
-
const { installTools: installTools2 } = await import("./enable-
|
|
11566
|
+
const { installTools: installTools2 } = await import("./enable-OICEMNUF.js");
|
|
11545
11567
|
installTools2(["workbuddy"], () => {
|
|
11546
11568
|
}, null, { auto: true });
|
|
11547
11569
|
} catch (e) {
|
|
@@ -12055,7 +12077,7 @@ function defaultOfferDeps(log) {
|
|
|
12055
12077
|
}),
|
|
12056
12078
|
openLogin: () => defaultOpenLogin(platform),
|
|
12057
12079
|
installHooks: async () => {
|
|
12058
|
-
const { installTools: installTools2 } = await import("./enable-
|
|
12080
|
+
const { installTools: installTools2 } = await import("./enable-OICEMNUF.js");
|
|
12059
12081
|
installTools2(["workbuddy"], (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`), null, { auto: true });
|
|
12060
12082
|
},
|
|
12061
12083
|
log: (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`),
|
|
@@ -12483,6 +12505,7 @@ export {
|
|
|
12483
12505
|
registerWorkbuddySession,
|
|
12484
12506
|
flushPendingWorkbuddy,
|
|
12485
12507
|
connectCdp,
|
|
12508
|
+
userIdleMs,
|
|
12486
12509
|
DEFAULT_CDP_PORT,
|
|
12487
12510
|
defaultExec,
|
|
12488
12511
|
APP_PORT_HUNG_REASON,
|
package/dist/cli.js
CHANGED
|
@@ -255,6 +255,7 @@ import {
|
|
|
255
255
|
uninstallAutostart,
|
|
256
256
|
uninstallShim,
|
|
257
257
|
unmarkWbAppSession,
|
|
258
|
+
userIdleMs,
|
|
258
259
|
versionCliPath,
|
|
259
260
|
versionDir,
|
|
260
261
|
waitModeFor,
|
|
@@ -278,15 +279,61 @@ import {
|
|
|
278
279
|
writeState,
|
|
279
280
|
writeWakeMark,
|
|
280
281
|
writeWakeSecret
|
|
281
|
-
} from "./chunk-
|
|
282
|
+
} from "./chunk-JPCZVBK6.js";
|
|
282
283
|
|
|
283
284
|
// src/commands/config.ts
|
|
285
|
+
var BOOL_KEYS = {
|
|
286
|
+
focusSessionOnRemote: {
|
|
287
|
+
def: false,
|
|
288
|
+
on: "\u5DF2\u6253\u5F00\uFF1A\u8BBE\u5907\u56DE\u590D / \u65B0\u5EFA\u4EFB\u52A1\u540E\uFF0C\u628A WorkBuddy\u3001ChatGPT\uFF08Codex\uFF09\u3001\u5343\u95EE\u529E\u516C\u5207\u5230\u8BE5\u4F1A\u8BDD\u5E76\u8C03\u5230\u524D\u53F0\uFF08\u4F60 10 \u79D2\u5185\u5728\u7528\u952E\u9F20\u65F6\u4E0D\u5207\uFF09",
|
|
289
|
+
off: "\u5DF2\u5173\u95ED\uFF1A\u8BBE\u5907\u56DE\u590D / \u65B0\u5EFA\u4EFB\u52A1\u540E\u4E0D\u518D\u628A App \u8C03\u5230\u524D\u53F0"
|
|
290
|
+
},
|
|
291
|
+
focusSessionOnRemoteWindows: {
|
|
292
|
+
def: false,
|
|
293
|
+
on: "\u5DF2\u6253\u5F00\uFF1AWindows \u4E0A\u4E5F\u7528\u6DF1\u94FE\u5207\u4F1A\u8BDD\uFF08\u8FD8\u987B\u6253\u5F00 focusSessionOnRemote\uFF1BApp \u6CA1\u6CE8\u518C\u534F\u8BAE\u7684\u7167\u6837\u4E0D\u5207\uFF09",
|
|
294
|
+
off: "\u5DF2\u5173\u95ED\uFF1AWindows \u4E0A\u4E0D\u7528\u6DF1\u94FE\u5207\u4F1A\u8BDD"
|
|
295
|
+
},
|
|
296
|
+
focusSessionInApp: {
|
|
297
|
+
def: true,
|
|
298
|
+
on: "\u5DF2\u6253\u5F00\uFF1A\u8BBE\u5907\u56DE\u590D opencode \u4F1A\u8BDD\u540E\uFF0C\u7EC8\u7AEF\u754C\u9762\u91CC\u5207\u5230\u8BE5\u4F1A\u8BDD\uFF08\u4E0D\u62A2\u7126\u70B9\uFF09",
|
|
299
|
+
off: "\u5DF2\u5173\u95ED\uFF1A\u8BBE\u5907\u56DE\u590D opencode \u4F1A\u8BDD\u540E\u4E0D\u5207\u6362\u754C\u9762\u91CC\u7684\u4F1A\u8BDD"
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
var isBoolKey = (k) => !!k && Object.prototype.hasOwnProperty.call(BOOL_KEYS, k);
|
|
303
|
+
var USAGE = [
|
|
304
|
+
"\u7528\u6CD5\uFF1Adeskbuddy config set transcript on|off \uFF08\u4F1A\u8BDD\u5BF9\u8BDD\u540C\u6B65\u7684\u672C\u5730\u5F00\u5173\uFF0C\u9ED8\u8BA4 on\uFF09",
|
|
305
|
+
" deskbuddy config get transcript",
|
|
306
|
+
` deskbuddy config set <${Object.keys(BOOL_KEYS).join("|")}> true|false`,
|
|
307
|
+
" deskbuddy config get <\u540C\u4E0A>"
|
|
308
|
+
].join("\n");
|
|
284
309
|
function runConfig(args, out = (s2) => process.stdout.write(s2 + "\n")) {
|
|
285
310
|
const [verb, key, value] = args;
|
|
286
|
-
if (
|
|
287
|
-
out(
|
|
311
|
+
if (verb !== "set" && verb !== "get") {
|
|
312
|
+
out(USAGE);
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
if (key === "transcript") return transcript(verb, value, out);
|
|
316
|
+
if (!isBoolKey(key)) {
|
|
317
|
+
out(USAGE);
|
|
288
318
|
return 1;
|
|
289
319
|
}
|
|
320
|
+
const spec = BOOL_KEYS[key];
|
|
321
|
+
const cfg = loadConfig();
|
|
322
|
+
if (verb === "get") {
|
|
323
|
+
out(String(cfg[key] ?? spec.def));
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
if (value !== "true" && value !== "false") {
|
|
327
|
+
out("\u53D6\u503C\u53EA\u80FD\u662F true \u6216 false");
|
|
328
|
+
return 1;
|
|
329
|
+
}
|
|
330
|
+
const v = value === "true";
|
|
331
|
+
const next = { ...cfg, [key]: v === spec.def ? void 0 : v };
|
|
332
|
+
saveConfig(next);
|
|
333
|
+
out(`${v ? spec.on : spec.off}\uFF08\u5B88\u62A4\u8FDB\u7A0B\u4E0B\u4E00\u6761\u6307\u4EE4\u751F\u6548\uFF09`);
|
|
334
|
+
return 0;
|
|
335
|
+
}
|
|
336
|
+
function transcript(verb, value, out) {
|
|
290
337
|
const cfg = loadConfig();
|
|
291
338
|
if (verb === "get") {
|
|
292
339
|
out(cfg.transcript === false ? "off" : "on");
|
|
@@ -818,6 +865,7 @@ import WebSocket2 from "ws";
|
|
|
818
865
|
|
|
819
866
|
// src/core/transcript/locate.ts
|
|
820
867
|
import { readdirSync as readdirSync2 } from "fs";
|
|
868
|
+
import { readdir } from "fs/promises";
|
|
821
869
|
import { join as join3 } from "path";
|
|
822
870
|
function findCodexRollout(codexHome2, sessionId, maxDays = 120) {
|
|
823
871
|
if (!/^[0-9A-Za-z-]{8,64}$/.test(sessionId)) return null;
|
|
@@ -848,6 +896,35 @@ function findCodexRollout(codexHome2, sessionId, maxDays = 120) {
|
|
|
848
896
|
}
|
|
849
897
|
return null;
|
|
850
898
|
}
|
|
899
|
+
async function findCodexRolloutAsync(codexHome2, sessionId, maxDays = 14) {
|
|
900
|
+
if (!/^[0-9A-Za-z-]{8,64}$/.test(sessionId)) return null;
|
|
901
|
+
const tail4 = `-${sessionId}.jsonl`;
|
|
902
|
+
const sorted = async (dir2) => {
|
|
903
|
+
try {
|
|
904
|
+
return (await readdir(dir2)).filter((n) => /^\d+$/.test(n)).sort().reverse();
|
|
905
|
+
} catch {
|
|
906
|
+
return [];
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
const root = join3(codexHome2, "sessions");
|
|
910
|
+
let days = 0;
|
|
911
|
+
for (const y of await sorted(root)) {
|
|
912
|
+
for (const m of await sorted(join3(root, y))) {
|
|
913
|
+
for (const d of await sorted(join3(root, y, m))) {
|
|
914
|
+
if (++days > maxDays) return null;
|
|
915
|
+
let names = [];
|
|
916
|
+
try {
|
|
917
|
+
names = await readdir(join3(root, y, m, d));
|
|
918
|
+
} catch {
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
const hit = names.find((n) => n.startsWith("rollout-") && n.endsWith(tail4));
|
|
922
|
+
if (hit) return join3(root, y, m, d, hit);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
851
928
|
|
|
852
929
|
// src/daemon/transcriptSync.ts
|
|
853
930
|
import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync2, readSync, renameSync, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -5260,7 +5337,19 @@ function createResult(text, isError) {
|
|
|
5260
5337
|
if (!v || v.success !== true || isError) return { status: "failed", reason: "window_failed", message: `\u5343\u95EE\u529E\u516C\u6CA1\u5EFA\u6210\u4EFB\u52A1\uFF1A${msg.slice(0, 120)}` };
|
|
5261
5338
|
const chatId = typeof v.chatId === "string" ? v.chatId : "";
|
|
5262
5339
|
if (!/^[A-Za-z0-9_-]{1,64}$/.test(chatId)) return { status: "failed", reason: "window_failed", message: "\u5343\u95EE\u529E\u516C\u5EFA\u4E86\u4EFB\u52A1\u4F46\u6CA1\u56DE\u4EFB\u52A1\u53F7" };
|
|
5263
|
-
|
|
5340
|
+
const subChatId = typeof v.subChatId === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(v.subChatId) ? v.subChatId : "";
|
|
5341
|
+
return subChatId ? { ok: true, chatId, subChatId } : { ok: true, chatId };
|
|
5342
|
+
}
|
|
5343
|
+
function qwenworkChatIdOfSession(sessionId, cwd, root = join14(qwenworkDataHome(), "workspace"), transcriptCwd = transcriptCwdOf) {
|
|
5344
|
+
let dir2 = typeof cwd === "string" ? cwd : "";
|
|
5345
|
+
if (!dir2 && sessionId) {
|
|
5346
|
+
try {
|
|
5347
|
+
dir2 = transcriptCwd(sessionId) ?? "";
|
|
5348
|
+
} catch {
|
|
5349
|
+
dir2 = "";
|
|
5350
|
+
}
|
|
5351
|
+
}
|
|
5352
|
+
return dir2 ? chatIdOf(dir2, root) : null;
|
|
5264
5353
|
}
|
|
5265
5354
|
function findQwenworkSession(chatId, root, dir2 = stateDir(), sinceMs = 0) {
|
|
5266
5355
|
let files;
|
|
@@ -5590,7 +5679,7 @@ var QwenWorkBridge = class {
|
|
|
5590
5679
|
}
|
|
5591
5680
|
const sessionId = await this.#waitSession(c.chatId, startedAt);
|
|
5592
5681
|
this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A\u8BBE\u5907\u65B0\u5EFA\u4EFB\u52A1 ${c.chatId.slice(0, 8)}${sessionId ? `\uFF0C\u5185\u6838\u4F1A\u8BDD ${sessionId.slice(0, 8)}` : "\uFF0C\u8FD8\u6CA1\u7B49\u5230\u5185\u6838\u4F1A\u8BDD\u53F7"}`);
|
|
5593
|
-
return { status: "started", chatId: c.chatId, sessionId };
|
|
5682
|
+
return { status: "started", chatId: c.chatId, sessionId, ...c.subChatId ? { subChatId: c.subChatId } : {} };
|
|
5594
5683
|
}
|
|
5595
5684
|
async #waitSession(chatId, sinceMs) {
|
|
5596
5685
|
const root = this.#d.workspaceRoot ?? join14(homedir3(), ".qwenworkcn", "workspace");
|
|
@@ -5890,6 +5979,252 @@ function sleep(ms) {
|
|
|
5890
5979
|
return new Promise((r) => setTimeout(r, ms).unref?.());
|
|
5891
5980
|
}
|
|
5892
5981
|
|
|
5982
|
+
// src/daemon/focusSession.ts
|
|
5983
|
+
import { execFile as execFile3 } from "child_process";
|
|
5984
|
+
import { open as open2 } from "fs/promises";
|
|
5985
|
+
var FOCUS_IDLE_MIN_MS = 10 * 1e3;
|
|
5986
|
+
var FOCUS_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
5987
|
+
var okId = (id) => typeof id === "string" && FOCUS_ID_RE.test(id);
|
|
5988
|
+
function workbuddyChatUrl(sessionId) {
|
|
5989
|
+
return okId(sessionId) ? `workbuddy://chat/${sessionId}` : null;
|
|
5990
|
+
}
|
|
5991
|
+
function codexThreadUrl(threadId) {
|
|
5992
|
+
return okId(threadId) ? `codex://threads/${threadId}` : null;
|
|
5993
|
+
}
|
|
5994
|
+
function qwenworkChatUrl(chatId, subChatId) {
|
|
5995
|
+
if (!okId(chatId)) return null;
|
|
5996
|
+
const q = new URLSearchParams({ chatId });
|
|
5997
|
+
if (subChatId && okId(subChatId)) q.set("subChatId", subChatId);
|
|
5998
|
+
return `qwenwork-cn://notification-click?${q.toString()}`;
|
|
5999
|
+
}
|
|
6000
|
+
function focusUrl(t) {
|
|
6001
|
+
if (t.kind === "workbuddy") return workbuddyChatUrl(t.sessionId);
|
|
6002
|
+
if (t.kind === "codex") return codexThreadUrl(t.threadId);
|
|
6003
|
+
return qwenworkChatUrl(t.chatId, t.subChatId);
|
|
6004
|
+
}
|
|
6005
|
+
function focusScheme(t) {
|
|
6006
|
+
return t.kind === "workbuddy" ? "workbuddy" : t.kind === "codex" ? "codex" : "qwenwork-cn";
|
|
6007
|
+
}
|
|
6008
|
+
var TOOL_NAME = { workbuddy: "WorkBuddy", codex: "ChatGPT\uFF08Codex\uFF09", qwenwork: "\u5343\u95EE\u529E\u516C" };
|
|
6009
|
+
function focusSettings(cfg) {
|
|
6010
|
+
return { onRemote: cfg.focusSessionOnRemote === true, onRemoteWindows: cfg.focusSessionOnRemoteWindows === true, inApp: cfg.focusSessionInApp !== false };
|
|
6011
|
+
}
|
|
6012
|
+
var APP_ORIGINATOR_RE = /"originator"\s*:\s*"(Codex Desktop|codex_work_desktop)"/;
|
|
6013
|
+
function isChatgptAppRolloutHead(head) {
|
|
6014
|
+
const first = head.split("\n", 1)[0] ?? "";
|
|
6015
|
+
return /"type"\s*:\s*"session_meta"/.test(first) && APP_ORIGINATOR_RE.test(first);
|
|
6016
|
+
}
|
|
6017
|
+
async function readHead(path, bytes = 16 * 1024) {
|
|
6018
|
+
let fh = null;
|
|
6019
|
+
try {
|
|
6020
|
+
fh = await open2(path, "r");
|
|
6021
|
+
const buf = Buffer.alloc(bytes);
|
|
6022
|
+
const { bytesRead } = await fh.read(buf, 0, bytes, 0);
|
|
6023
|
+
return buf.subarray(0, bytesRead).toString("utf8");
|
|
6024
|
+
} catch {
|
|
6025
|
+
return null;
|
|
6026
|
+
} finally {
|
|
6027
|
+
await fh?.close().catch(() => {
|
|
6028
|
+
});
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
var CODEX_OWNER_MAX_DAYS = 14;
|
|
6032
|
+
async function codexThreadInChatgptApp(threadId, d = {}) {
|
|
6033
|
+
if (!okId(threadId)) return false;
|
|
6034
|
+
try {
|
|
6035
|
+
if (d.seenByApp?.(threadId)) return true;
|
|
6036
|
+
} catch {
|
|
6037
|
+
}
|
|
6038
|
+
let file = null;
|
|
6039
|
+
try {
|
|
6040
|
+
file = await (d.rollout ?? ((id) => findCodexRolloutAsync(codexHome(), id, CODEX_OWNER_MAX_DAYS)))(threadId);
|
|
6041
|
+
} catch {
|
|
6042
|
+
file = null;
|
|
6043
|
+
}
|
|
6044
|
+
if (!file) return false;
|
|
6045
|
+
let head = null;
|
|
6046
|
+
try {
|
|
6047
|
+
head = await (d.readHead ?? readHead)(file);
|
|
6048
|
+
} catch {
|
|
6049
|
+
head = null;
|
|
6050
|
+
}
|
|
6051
|
+
return !!head && isChatgptAppRolloutHead(head);
|
|
6052
|
+
}
|
|
6053
|
+
var skipSystem = () => process.env.DESKBUDDY_SKIP_FOCUS_OPEN === "1";
|
|
6054
|
+
function execOk(file, args, timeoutMs = 1e4) {
|
|
6055
|
+
return new Promise((done) => {
|
|
6056
|
+
try {
|
|
6057
|
+
execFile3(file, args, { timeout: timeoutMs, windowsHide: true }, (err) => done(!err));
|
|
6058
|
+
} catch {
|
|
6059
|
+
done(false);
|
|
6060
|
+
}
|
|
6061
|
+
});
|
|
6062
|
+
}
|
|
6063
|
+
function defaultOpenUrl(url, platform = process.platform) {
|
|
6064
|
+
if (skipSystem()) return Promise.resolve(false);
|
|
6065
|
+
if (platform === "darwin") return execOk("open", [url]);
|
|
6066
|
+
if (platform === "win32") return execOk("rundll32.exe", ["url.dll,FileProtocolHandler", url]);
|
|
6067
|
+
return Promise.resolve(false);
|
|
6068
|
+
}
|
|
6069
|
+
function defaultProtocolRegistered(scheme) {
|
|
6070
|
+
if (skipSystem() || !/^[a-z][a-z0-9+.-]{0,31}$/.test(scheme)) return Promise.resolve(false);
|
|
6071
|
+
return execOk("reg.exe", ["query", `HKCR\\${scheme}`, "/v", "URL Protocol"], 5e3);
|
|
6072
|
+
}
|
|
6073
|
+
var idleWhy = (idle) => idle === null ? "\u8BFB\u4E0D\u5230\u952E\u9F20\u7A7A\u95F2\u65F6\u957F" : `\u7528\u6237\u6B63\u5728\u7528\u7535\u8111\uFF08${Math.round(idle / 1e3)} \u79D2\u524D\u78B0\u8FC7\u952E\u9F20\uFF09`;
|
|
6074
|
+
var DONE_MAX = 200;
|
|
6075
|
+
var IDLE_REFRESH_MAC_MS = 5 * 1e3;
|
|
6076
|
+
var IDLE_REFRESH_WIN_MS = 30 * 1e3;
|
|
6077
|
+
var IDLE_ACTIVE_WINDOW_MS = 10 * 60 * 1e3;
|
|
6078
|
+
var SessionFocuser = class {
|
|
6079
|
+
#d;
|
|
6080
|
+
#done = /* @__PURE__ */ new Set();
|
|
6081
|
+
/** 后台取到的键鼠空闲(opencode 切会话只读它,投递永不等取空闲,评审 73f95f0) */
|
|
6082
|
+
#idleCache = null;
|
|
6083
|
+
#lastInAppUse = Number.NEGATIVE_INFINITY;
|
|
6084
|
+
#idleTimer = null;
|
|
6085
|
+
#refreshing = false;
|
|
6086
|
+
constructor(d) {
|
|
6087
|
+
this.#d = d;
|
|
6088
|
+
}
|
|
6089
|
+
/** 刷新周期:Windows 30 秒,其余 5 秒 */
|
|
6090
|
+
idleRefreshMs() {
|
|
6091
|
+
return this.#platform() === "win32" ? IDLE_REFRESH_WIN_MS : IDLE_REFRESH_MAC_MS;
|
|
6092
|
+
}
|
|
6093
|
+
/**
|
|
6094
|
+
* 后台刷新一次空闲缓存:只在 focusSessionInApp 开着、且最近 10 分钟内有过 opencode 投递时取;同一时刻只取一次。
|
|
6095
|
+
* 不活跃了就停掉定时器(下次投递再起)
|
|
6096
|
+
*/
|
|
6097
|
+
async refreshIdle() {
|
|
6098
|
+
if (this.#refreshing) return;
|
|
6099
|
+
if (!this.inAppEnabled() || this.#now() - this.#lastInAppUse > IDLE_ACTIVE_WINDOW_MS) {
|
|
6100
|
+
this.#stopIdleTimer();
|
|
6101
|
+
return;
|
|
6102
|
+
}
|
|
6103
|
+
this.#refreshing = true;
|
|
6104
|
+
try {
|
|
6105
|
+
const ms = await this.#idle();
|
|
6106
|
+
this.#idleCache = ms === null ? null : { ms, at: this.#now() };
|
|
6107
|
+
} finally {
|
|
6108
|
+
this.#refreshing = false;
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
stop() {
|
|
6112
|
+
this.#stopIdleTimer();
|
|
6113
|
+
}
|
|
6114
|
+
#stopIdleTimer() {
|
|
6115
|
+
if (this.#idleTimer) clearInterval(this.#idleTimer);
|
|
6116
|
+
this.#idleTimer = null;
|
|
6117
|
+
}
|
|
6118
|
+
/** 缓存里的空闲:没有、或超过刷新周期 2 倍 → null(当读不到) */
|
|
6119
|
+
#cachedIdle() {
|
|
6120
|
+
const c = this.#idleCache;
|
|
6121
|
+
if (!c || this.#now() - c.at > 2 * this.idleRefreshMs()) return null;
|
|
6122
|
+
return c.ms;
|
|
6123
|
+
}
|
|
6124
|
+
/** opencode 这类 App 内切换(不激活)开着吗 */
|
|
6125
|
+
inAppEnabled() {
|
|
6126
|
+
return focusSettings(this.#cfg()).inApp;
|
|
6127
|
+
}
|
|
6128
|
+
/**
|
|
6129
|
+
* opencode 这类 App 内切换现在能不能做:开关开着,且用户没在用电脑(键鼠空闲 ≥ FOCUS_IDLE_MIN_MS;读不到也不切)。
|
|
6130
|
+
* 🔴 同步、只读后台缓存,绝不在投递路径上取空闲(Windows 要起 PowerShell)。缓存没有 / 过期 → 这次不切;
|
|
6131
|
+
* 同时记下「最近有 opencode 投递」并确保后台刷新在跑(第一次投递时还没有缓存,从下一条起生效)。跳过时记一行
|
|
6132
|
+
*/
|
|
6133
|
+
inAppReady(instructionId, name = "opencode") {
|
|
6134
|
+
if (!this.inAppEnabled()) return false;
|
|
6135
|
+
this.#lastInAppUse = this.#now();
|
|
6136
|
+
if (!this.#idleTimer && this.#d.timers !== false) {
|
|
6137
|
+
void this.refreshIdle().catch(() => {
|
|
6138
|
+
});
|
|
6139
|
+
this.#idleTimer = setInterval(() => void this.refreshIdle().catch(() => {
|
|
6140
|
+
}), this.idleRefreshMs());
|
|
6141
|
+
this.#idleTimer.unref?.();
|
|
6142
|
+
}
|
|
6143
|
+
const idle = this.#cachedIdle();
|
|
6144
|
+
if (idle === null || idle < FOCUS_IDLE_MIN_MS) {
|
|
6145
|
+
this.#d.log(`\u6307\u4EE4 ${instructionId} \u4E0D\u5728 ${name} \u754C\u9762\u91CC\u5207\u4F1A\u8BDD\uFF1A${idleWhy(idle)}`);
|
|
6146
|
+
return false;
|
|
6147
|
+
}
|
|
6148
|
+
return true;
|
|
6149
|
+
}
|
|
6150
|
+
/** 深链切会话在这台电脑上会生效吗(开关 + 平台)。调用方据此决定要不要先做查找类的准备工作 */
|
|
6151
|
+
remoteEnabled() {
|
|
6152
|
+
const s2 = focusSettings(this.#cfg());
|
|
6153
|
+
if (!s2.onRemote) return false;
|
|
6154
|
+
const p = this.#platform();
|
|
6155
|
+
return p === "darwin" || p === "win32" && s2.onRemoteWindows;
|
|
6156
|
+
}
|
|
6157
|
+
/**
|
|
6158
|
+
* 设备回复 / 新建任务交出去之后调:开关、平台、id、用户在不在打字都过了才打开深链。永不抛。
|
|
6159
|
+
* instructionId 只用来去重和记日志;不记会话内容
|
|
6160
|
+
*/
|
|
6161
|
+
async focus(instructionId, t) {
|
|
6162
|
+
if (this.#done.has(instructionId)) return "duplicate";
|
|
6163
|
+
const s2 = focusSettings(this.#cfg());
|
|
6164
|
+
if (!s2.onRemote) return "disabled";
|
|
6165
|
+
const p = this.#platform();
|
|
6166
|
+
if (p !== "darwin" && !(p === "win32" && s2.onRemoteWindows)) return "platform";
|
|
6167
|
+
const url = focusUrl(t);
|
|
6168
|
+
const name = TOOL_NAME[t.kind];
|
|
6169
|
+
if (!url) {
|
|
6170
|
+
this.#d.log(`\u6307\u4EE4 ${instructionId} \u5207\u5230 ${name} \u4F1A\u8BDD\uFF1A\u4F1A\u8BDD\u53F7\u4E0D\u5408\u6CD5\uFF0C\u4E0D\u5207`);
|
|
6171
|
+
return "bad_id";
|
|
6172
|
+
}
|
|
6173
|
+
this.#remember(instructionId);
|
|
6174
|
+
if (p === "win32") {
|
|
6175
|
+
let reg = false;
|
|
6176
|
+
try {
|
|
6177
|
+
reg = await (this.#d.protocolRegistered ?? defaultProtocolRegistered)(focusScheme(t));
|
|
6178
|
+
} catch {
|
|
6179
|
+
reg = false;
|
|
6180
|
+
}
|
|
6181
|
+
if (!reg) {
|
|
6182
|
+
this.#d.log(`\u6307\u4EE4 ${instructionId} \u5207\u5230 ${name} \u4F1A\u8BDD\u5931\u8D25\uFF1A\u672C\u673A\u6CA1\u6CE8\u518C ${focusScheme(t)}:// \u534F\u8BAE`);
|
|
6183
|
+
return "protocol_missing";
|
|
6184
|
+
}
|
|
6185
|
+
}
|
|
6186
|
+
const idle = await this.#idle();
|
|
6187
|
+
if (idle === null || idle < FOCUS_IDLE_MIN_MS) {
|
|
6188
|
+
this.#d.log(`\u6307\u4EE4 ${instructionId} \u4E0D\u5207\u5230 ${name} \u4F1A\u8BDD\uFF1A${idleWhy(idle)}`);
|
|
6189
|
+
return "user_active";
|
|
6190
|
+
}
|
|
6191
|
+
let ok = false;
|
|
6192
|
+
try {
|
|
6193
|
+
ok = await (this.#d.openUrl ?? ((u) => defaultOpenUrl(u, p)))(url);
|
|
6194
|
+
} catch {
|
|
6195
|
+
ok = false;
|
|
6196
|
+
}
|
|
6197
|
+
this.#d.log(ok ? `\u6307\u4EE4 ${instructionId} \u5DF2\u5207\u5230 ${name} \u4F1A\u8BDD\uFF08\u6DF1\u94FE\uFF09` : `\u6307\u4EE4 ${instructionId} \u5207\u5230 ${name} \u4F1A\u8BDD\u5931\u8D25\uFF1A\u6DF1\u94FE\u6CA1\u6253\u5F00`);
|
|
6198
|
+
return ok ? "opened" : "failed";
|
|
6199
|
+
}
|
|
6200
|
+
/** 键鼠空闲毫秒(Windows 起一次 PowerShell);取不到 null。永不抛 */
|
|
6201
|
+
async #idle() {
|
|
6202
|
+
const p = this.#platform();
|
|
6203
|
+
try {
|
|
6204
|
+
return await (this.#d.idleMs ?? (() => skipSystem() ? Promise.resolve(null) : userIdleMs(defaultExec, p)))();
|
|
6205
|
+
} catch {
|
|
6206
|
+
return null;
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
#remember(id) {
|
|
6210
|
+
this.#done.add(id);
|
|
6211
|
+
if (this.#done.size > DONE_MAX) this.#done.delete(this.#done.values().next().value);
|
|
6212
|
+
}
|
|
6213
|
+
#cfg() {
|
|
6214
|
+
try {
|
|
6215
|
+
return (this.#d.config ?? loadConfig)();
|
|
6216
|
+
} catch {
|
|
6217
|
+
return {};
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
#now() {
|
|
6221
|
+
return this.#d.now ? this.#d.now() : Date.now();
|
|
6222
|
+
}
|
|
6223
|
+
#platform() {
|
|
6224
|
+
return this.#d.platform ?? process.platform;
|
|
6225
|
+
}
|
|
6226
|
+
};
|
|
6227
|
+
|
|
5893
6228
|
// src/daemon/qwenworkApp/watch.ts
|
|
5894
6229
|
import { homedir as homedir4 } from "os";
|
|
5895
6230
|
import { join as join15 } from "path";
|
|
@@ -10715,13 +11050,13 @@ function replyMac(secret, nonceC, r) {
|
|
|
10715
11050
|
function startChannelServer(path, secret, onRequest, platform = process.platform, onAnswer) {
|
|
10716
11051
|
return new Promise((resolve8) => {
|
|
10717
11052
|
const server = createServer3((sock) => {
|
|
10718
|
-
let
|
|
11053
|
+
let open4 = true;
|
|
10719
11054
|
let phase = "hello";
|
|
10720
11055
|
let nonceC = "";
|
|
10721
11056
|
let nonceS = "";
|
|
10722
11057
|
sock.on("error", () => {
|
|
10723
11058
|
});
|
|
10724
|
-
sock.on("close", () =>
|
|
11059
|
+
sock.on("close", () => open4 = false);
|
|
10725
11060
|
const authTimer = setTimeout(() => {
|
|
10726
11061
|
if (phase === "hello") sock.destroy();
|
|
10727
11062
|
}, 5e3);
|
|
@@ -10766,7 +11101,7 @@ function startChannelServer(path, secret, onRequest, platform = process.platform
|
|
|
10766
11101
|
return;
|
|
10767
11102
|
}
|
|
10768
11103
|
phase = "done";
|
|
10769
|
-
onRequest(req, reply, () =>
|
|
11104
|
+
onRequest(req, reply, () => open4);
|
|
10770
11105
|
} catch {
|
|
10771
11106
|
sock.destroy();
|
|
10772
11107
|
}
|
|
@@ -11006,10 +11341,10 @@ function toolOfMessage(message) {
|
|
|
11006
11341
|
}
|
|
11007
11342
|
function pendingCall(text, toolName) {
|
|
11008
11343
|
const { calls, done } = scanTranscript(text);
|
|
11009
|
-
const
|
|
11010
|
-
const last =
|
|
11344
|
+
const open4 = calls.filter((c) => !done.has(c.callId));
|
|
11345
|
+
const last = open4[open4.length - 1];
|
|
11011
11346
|
if (!last || last.name !== toolName) return null;
|
|
11012
|
-
if (
|
|
11347
|
+
if (open4.filter((c) => c.name === toolName).length > 1) return null;
|
|
11013
11348
|
return last;
|
|
11014
11349
|
}
|
|
11015
11350
|
function selectionsOf(input, lists) {
|
|
@@ -11088,17 +11423,17 @@ var WorkbuddyBridge = class {
|
|
|
11088
11423
|
if (h.hook_event_name !== "Notification" || h.notification_type !== "permission_prompt") return;
|
|
11089
11424
|
const sessionId = typeof h.session_id === "string" ? h.session_id : "";
|
|
11090
11425
|
const windowId = typeof h.deskbuddy_window === "string" ? h.deskbuddy_window : "";
|
|
11091
|
-
const
|
|
11092
|
-
if (!sessionId || !/^[a-f0-9]{32}$/.test(windowId) || !
|
|
11426
|
+
const transcript2 = typeof h.transcript_path === "string" ? h.transcript_path : "";
|
|
11427
|
+
if (!sessionId || !/^[a-f0-9]{32}$/.test(windowId) || !transcript2) return;
|
|
11093
11428
|
const rec = this.#window(windowId);
|
|
11094
11429
|
if (!rec) return;
|
|
11095
11430
|
const toolName = toolOfMessage(h.message);
|
|
11096
11431
|
if (!toolName) return;
|
|
11097
|
-
if (!(this.#d.transcriptOk ?? ((p) => transcriptAllowed(p)))(
|
|
11432
|
+
if (!(this.#d.transcriptOk ?? ((p) => transcriptAllowed(p)))(transcript2)) {
|
|
11098
11433
|
this.#d.log(`WorkBuddy\uFF1A\u4F1A\u8BDD ${sessionId.slice(0, 8)} \u7684\u8BB0\u5F55\u4E0D\u5728 WorkBuddy \u914D\u7F6E\u76EE\u5F55\u4E0B\uFF0C\u4E0D\u63A8\u8BBE\u5907`);
|
|
11099
11434
|
return;
|
|
11100
11435
|
}
|
|
11101
|
-
const text = this.#read(
|
|
11436
|
+
const text = this.#read(transcript2);
|
|
11102
11437
|
const call = text ? pendingCall(text, toolName) : null;
|
|
11103
11438
|
if (!call) {
|
|
11104
11439
|
this.#d.log(`WorkBuddy\uFF1A\u4F1A\u8BDD ${sessionId.slice(0, 8)} \u7684 ${toolName} \u5728\u8BB0\u5F55\u91CC\u627E\u4E0D\u5230\u5F85\u7B54\u7684\u90A3\u4E00\u6B65\uFF0C\u4E0D\u63A8\u8BBE\u5907`);
|
|
@@ -11128,7 +11463,7 @@ var WorkbuddyBridge = class {
|
|
|
11128
11463
|
this.#cards.delete(sessionId);
|
|
11129
11464
|
old.ac.abort();
|
|
11130
11465
|
}
|
|
11131
|
-
const card = { key, sessionId, windowId, callId: call.callId, toolName, fingerprint: fingerprint2, input: call.input, transcript, ac: new AbortController(), misses: 0 };
|
|
11466
|
+
const card = { key, sessionId, windowId, callId: call.callId, toolName, fingerprint: fingerprint2, input: call.input, transcript: transcript2, ac: new AbortController(), misses: 0 };
|
|
11132
11467
|
this.#cards.set(sessionId, card);
|
|
11133
11468
|
this.#ensurePoll();
|
|
11134
11469
|
const gateReq = {
|
|
@@ -11140,7 +11475,7 @@ var WorkbuddyBridge = class {
|
|
|
11140
11475
|
cwd: typeof h.cwd === "string" ? h.cwd : rec.cwd,
|
|
11141
11476
|
tool_name: toolName,
|
|
11142
11477
|
tool_input: call.input,
|
|
11143
|
-
transcript_path:
|
|
11478
|
+
transcript_path: transcript2
|
|
11144
11479
|
}
|
|
11145
11480
|
};
|
|
11146
11481
|
let reply;
|
|
@@ -11609,9 +11944,9 @@ var CodebuddyTerminal = class {
|
|
|
11609
11944
|
const onAbort = () => ac.abort(signal.reason);
|
|
11610
11945
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
11611
11946
|
let timer;
|
|
11612
|
-
const
|
|
11613
|
-
if (
|
|
11614
|
-
const read = () => (this.#d.readTranscript ?? tail3)(
|
|
11947
|
+
const transcript2 = sess ? sess.transcript : "";
|
|
11948
|
+
if (transcript2 && (this.#d.transcriptOk ?? ((p) => transcriptAllowed(p)))(transcript2)) {
|
|
11949
|
+
const read = () => (this.#d.readTranscript ?? tail3)(transcript2);
|
|
11615
11950
|
let callId = a ? a.toolUseId : "";
|
|
11616
11951
|
if (!callId) {
|
|
11617
11952
|
const t0 = read();
|
|
@@ -12416,7 +12751,7 @@ function pidAlive4(pid) {
|
|
|
12416
12751
|
}
|
|
12417
12752
|
|
|
12418
12753
|
// src/daemon/dumateApp/transport.ts
|
|
12419
|
-
import { execFile as
|
|
12754
|
+
import { execFile as execFile4 } from "child_process";
|
|
12420
12755
|
import { win32 as win323 } from "path";
|
|
12421
12756
|
|
|
12422
12757
|
// src/daemon/dumateApp/page.ts
|
|
@@ -12636,7 +12971,7 @@ function isWinAppMain(r) {
|
|
|
12636
12971
|
}
|
|
12637
12972
|
function execOut(file, args, timeoutMs) {
|
|
12638
12973
|
return new Promise((done) => {
|
|
12639
|
-
|
|
12974
|
+
execFile4(file, args, { timeout: timeoutMs, windowsHide: true, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }, (err, stdout6) => done(err ? null : String(stdout6 ?? "")));
|
|
12640
12975
|
});
|
|
12641
12976
|
}
|
|
12642
12977
|
async function defaultBackend(platform = process.platform) {
|
|
@@ -14181,6 +14516,7 @@ function codexQuestionInput(p) {
|
|
|
14181
14516
|
var INJECT_VERSION = 2;
|
|
14182
14517
|
var QUEUE_MAX2 = 500;
|
|
14183
14518
|
var RESOLVED_MAX = 200;
|
|
14519
|
+
var SEEN_THREADS_MAX = 500;
|
|
14184
14520
|
var KEEPER_EVERY_MS5 = 15 * 1e3;
|
|
14185
14521
|
var LAUNCH_CHECK_EVERY_MS2 = 2 * 1e3;
|
|
14186
14522
|
var CONNECT_BACKOFF_MIN_MS5 = 15 * 1e3;
|
|
@@ -14285,6 +14621,8 @@ var ChatgptAppBridge = class {
|
|
|
14285
14621
|
#stopped = false;
|
|
14286
14622
|
/** 会话 → 最近一次状态(thread/status/changed);有 active 的 = App 忙 */
|
|
14287
14623
|
#status = /* @__PURE__ */ new Map();
|
|
14624
|
+
/** 页面里见过的线程(设备回复后切会话时判「这个线程属于 App」,spec 2026-10-13 focus-session §2.2);断开不清,最多 SEEN_THREADS_MAX 条 */
|
|
14625
|
+
#seenThreads = /* @__PURE__ */ new Set();
|
|
14288
14626
|
constructor(deps2) {
|
|
14289
14627
|
this.#d = deps2;
|
|
14290
14628
|
}
|
|
@@ -14299,6 +14637,15 @@ var ChatgptAppBridge = class {
|
|
|
14299
14637
|
for (const t of this.#status.values()) if (t === "active") return true;
|
|
14300
14638
|
return false;
|
|
14301
14639
|
}
|
|
14640
|
+
/** 页面桥收到过这个线程的动静吗(线程属于 ChatGPT App) */
|
|
14641
|
+
seenThread(threadId) {
|
|
14642
|
+
return this.#seenThreads.has(threadId);
|
|
14643
|
+
}
|
|
14644
|
+
#noteThread(threadId) {
|
|
14645
|
+
this.#seenThreads.delete(threadId);
|
|
14646
|
+
this.#seenThreads.add(threadId);
|
|
14647
|
+
if (this.#seenThreads.size > SEEN_THREADS_MAX) this.#seenThreads.delete(this.#seenThreads.values().next().value);
|
|
14648
|
+
}
|
|
14302
14649
|
/** 等设备答复的卡有几张 */
|
|
14303
14650
|
pending() {
|
|
14304
14651
|
return this.#channel?.pending() ?? 0;
|
|
@@ -14437,11 +14784,14 @@ var ChatgptAppBridge = class {
|
|
|
14437
14784
|
const id = req.id;
|
|
14438
14785
|
if (typeof req.method !== "string" || !REQUEST_METHODS.includes(req.method)) return;
|
|
14439
14786
|
if (typeof id !== "number" && typeof id !== "string") return;
|
|
14787
|
+
const tid = req.params && typeof req.params === "object" ? req.params.threadId : void 0;
|
|
14788
|
+
if (typeof tid === "string" && tid) this.#noteThread(tid);
|
|
14440
14789
|
ch.onMessage({ id, method: req.method, params: req.params });
|
|
14441
14790
|
return;
|
|
14442
14791
|
}
|
|
14443
14792
|
if (m.type === "mcp-notification" && typeof m.method === "string") {
|
|
14444
14793
|
const p = m.params && typeof m.params === "object" ? m.params : {};
|
|
14794
|
+
if (typeof p.threadId === "string" && p.threadId) this.#noteThread(p.threadId);
|
|
14445
14795
|
if (m.method === "thread/status/changed" && typeof p.threadId === "string") {
|
|
14446
14796
|
const type2 = (p.status ?? {}).type;
|
|
14447
14797
|
if (typeof type2 === "string") this.#status.set(p.threadId, type2);
|
|
@@ -14821,10 +15171,10 @@ function codexInterruptedAt(file) {
|
|
|
14821
15171
|
}
|
|
14822
15172
|
|
|
14823
15173
|
// src/daemon/qoderAppDb.ts
|
|
14824
|
-
import { execFile as
|
|
15174
|
+
import { execFile as execFile5 } from "child_process";
|
|
14825
15175
|
import { createHash as createHash3 } from "crypto";
|
|
14826
15176
|
import { existsSync as existsSync6 } from "fs";
|
|
14827
|
-
import { access, open as
|
|
15177
|
+
import { access, open as open3, readdir as readdir2, stat } from "fs/promises";
|
|
14828
15178
|
import { createRequire } from "module";
|
|
14829
15179
|
import { basename as basename8, dirname as dirname7, isAbsolute as isAbsolute3, join as join24 } from "path";
|
|
14830
15180
|
var QODER_APP_TOOLS = ["qoder_cn", "qoder"];
|
|
@@ -14906,7 +15256,7 @@ async function findTranscript(configDir, sessionId) {
|
|
|
14906
15256
|
const root = join24(configDir, "projects");
|
|
14907
15257
|
let dirs;
|
|
14908
15258
|
try {
|
|
14909
|
-
dirs = await
|
|
15259
|
+
dirs = await readdir2(root);
|
|
14910
15260
|
} catch {
|
|
14911
15261
|
return null;
|
|
14912
15262
|
}
|
|
@@ -14922,7 +15272,7 @@ async function findTranscript(configDir, sessionId) {
|
|
|
14922
15272
|
}
|
|
14923
15273
|
var SLICE = 64 * 1024;
|
|
14924
15274
|
async function readSlice(p, from) {
|
|
14925
|
-
const fh = await
|
|
15275
|
+
const fh = await open3(p, "r");
|
|
14926
15276
|
try {
|
|
14927
15277
|
const size = (await fh.stat()).size;
|
|
14928
15278
|
const len = Math.min(size, SLICE);
|
|
@@ -15004,7 +15354,7 @@ var defaultQoderAppDeps = () => ({
|
|
|
15004
15354
|
},
|
|
15005
15355
|
runElectron: (bin, script, input) => new Promise((resolve8, reject) => {
|
|
15006
15356
|
const env = { ...process.env, ELECTRON_RUN_AS_NODE: "1" };
|
|
15007
|
-
const c =
|
|
15357
|
+
const c = execFile5(
|
|
15008
15358
|
bin,
|
|
15009
15359
|
["-e", script],
|
|
15010
15360
|
{ env, timeout: 15e3, windowsHide: true, maxBuffer: 1024 * 1024 },
|
|
@@ -15889,7 +16239,7 @@ function scheduleCcTemplateCheck(deps2 = defaultCcCheckDeps, delayMs = CC_CHECK_
|
|
|
15889
16239
|
}
|
|
15890
16240
|
|
|
15891
16241
|
// src/daemon/opencodeVersion.ts
|
|
15892
|
-
import { execFile as
|
|
16242
|
+
import { execFile as execFile6 } from "child_process";
|
|
15893
16243
|
import { statSync as statSync15 } from "fs";
|
|
15894
16244
|
var OPENCODE_PROMPT_MIN_VERSION = "1.2.25";
|
|
15895
16245
|
function parseOpencodeVersion(out) {
|
|
@@ -15960,7 +16310,7 @@ var defaultOpencodeVersion = createOpencodeVersion({
|
|
|
15960
16310
|
},
|
|
15961
16311
|
run: (loc) => new Promise((resolve8) => {
|
|
15962
16312
|
try {
|
|
15963
|
-
|
|
16313
|
+
execFile6(
|
|
15964
16314
|
loc.file,
|
|
15965
16315
|
[...loc.prefixArgs, "--version"],
|
|
15966
16316
|
{ encoding: "utf8", windowsHide: true, timeout: PROBE_TIMEOUT_MS, env: { ...scrubAgentEnv({ ...process.env }), ...loc.env ?? {} } },
|
|
@@ -16559,7 +16909,9 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
|
|
|
16559
16909
|
text: msg.text,
|
|
16560
16910
|
at: writtenAt,
|
|
16561
16911
|
...d0.addressed ? { pid: hb.pid } : {},
|
|
16562
|
-
...msg.summary ? { summary: true, replyWaitMs: msg.replyWaitMs ?? DSH_REPLY_WAIT_MS } : {}
|
|
16912
|
+
...msg.summary ? { summary: true, replyWaitMs: msg.replyWaitMs ?? DSH_REPLY_WAIT_MS } : {},
|
|
16913
|
+
// opencode:投递成功后在终端界面里切到这个会话(spec 2026-10-13 focus-session §1);dsh 插件不认这个字段
|
|
16914
|
+
...msg.focus ? { focus: true } : {}
|
|
16563
16915
|
});
|
|
16564
16916
|
const sleep3 = d0.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
16565
16917
|
let deadline = writtenAt + (d0.timeoutMs ?? DSH_ACK_TIMEOUT_MS);
|
|
@@ -16960,7 +17312,9 @@ var Daemon = class {
|
|
|
16960
17312
|
if (offer) void offer.maybeAsk("runtime").catch(() => {
|
|
16961
17313
|
});
|
|
16962
17314
|
}),
|
|
16963
|
-
workbuddyAppSync: deps2.workbuddyAppSync ?? ((job) => registerWorkbuddySession(job))
|
|
17315
|
+
workbuddyAppSync: deps2.workbuddyAppSync ?? ((job) => registerWorkbuddySession(job)),
|
|
17316
|
+
focuser: deps2.focuser ?? new SessionFocuser({ log }),
|
|
17317
|
+
codexInApp: deps2.codexInApp ?? ((tid) => codexThreadInChatgptApp(tid, { seenByApp: (id) => this.#chatgptApp?.seenThread(id) === true }))
|
|
16964
17318
|
};
|
|
16965
17319
|
this.#wake = new WakeHub({
|
|
16966
17320
|
now: this.#deps.now,
|
|
@@ -17623,6 +17977,7 @@ var Daemon = class {
|
|
|
17623
17977
|
stop() {
|
|
17624
17978
|
this.#stopped = true;
|
|
17625
17979
|
this.#clearIdle();
|
|
17980
|
+
this.#deps.focuser.stop?.();
|
|
17626
17981
|
if (this.#windowScan) clearInterval(this.#windowScan);
|
|
17627
17982
|
if (this.#aliveReport) clearInterval(this.#aliveReport);
|
|
17628
17983
|
if (this.#memLog) clearInterval(this.#memLog);
|
|
@@ -18077,7 +18432,10 @@ var Daemon = class {
|
|
|
18077
18432
|
*/
|
|
18078
18433
|
#toWbApp(cmd, sid, cwd) {
|
|
18079
18434
|
const key = sid ? `wb:${sid}` : `wbnew:${cmd.instructionId}`;
|
|
18080
|
-
const report = (status, sessionId, reason) =>
|
|
18435
|
+
const report = (status, sessionId, reason) => {
|
|
18436
|
+
this.#report(cmd, sessionId, status, "window", reason ?? "");
|
|
18437
|
+
if (status === "started" && sessionId) this.#focusAfter(cmd, () => ({ kind: "workbuddy", sessionId }));
|
|
18438
|
+
};
|
|
18081
18439
|
const run = async () => {
|
|
18082
18440
|
let r;
|
|
18083
18441
|
try {
|
|
@@ -18445,14 +18803,16 @@ var Daemon = class {
|
|
|
18445
18803
|
*/
|
|
18446
18804
|
async #toOcWindow(cmd, sid) {
|
|
18447
18805
|
let ack;
|
|
18806
|
+
const focus = !isSummary(cmd) && this.#focusInApp(cmd);
|
|
18448
18807
|
try {
|
|
18449
|
-
ack = await this.#deps.ocDeliver(sid, { instructionId: cmd.instructionId, text: cmd.text, ...isSummary(cmd) ? { summary: true } : {} });
|
|
18808
|
+
ack = await this.#deps.ocDeliver(sid, { instructionId: cmd.instructionId, text: cmd.text, ...isSummary(cmd) ? { summary: true } : {}, ...focus ? { focus: true } : {} });
|
|
18450
18809
|
} catch (e) {
|
|
18451
18810
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u6295\u9012\u7ED9 opencode \u51FA\u9519\uFF1A${e.message}`);
|
|
18452
18811
|
ack = { ok: false, reason: "deliver_error" };
|
|
18453
18812
|
}
|
|
18454
18813
|
if (ack.ok) {
|
|
18455
18814
|
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}`);
|
|
18815
|
+
if (focus) log(`\u6307\u4EE4 ${cmd.instructionId} \u5DF2\u8BF7\u63D2\u4EF6\u5728 opencode \u754C\u9762\u91CC\u5207\u5230\u8FD9\u4E2A\u4F1A\u8BDD\uFF08\u4F4E\u7248\u672C\u63D2\u4EF6\u81EA\u5DF1\u8DF3\u8FC7\uFF09`);
|
|
18456
18816
|
if (isSummary(cmd)) this.#summaryWatch(cmd, sid, "ocMailbox");
|
|
18457
18817
|
this.#report(cmd, sid, ack.busy ? "queued" : "started", "window");
|
|
18458
18818
|
return;
|
|
@@ -18536,6 +18896,12 @@ var Daemon = class {
|
|
|
18536
18896
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u5DF2\u53D1\u8FDB${name}\u4F1A\u8BDD ${sid.slice(0, 12)}${r.status === "queued" ? "\uFF08\u6B63\u5FD9\uFF0C\u6392\u8FDB\u4E0B\u4E00\u8F6E\uFF09" : ""} len=${cmd.text.length}`);
|
|
18537
18897
|
if (isSummary(cmd) && tool === "dumate") this.#summaryWatch(cmd, sid, "dumateHttp");
|
|
18538
18898
|
this.#report(cmd, sid, r.status, "window");
|
|
18899
|
+
if (tool === "qwenwork")
|
|
18900
|
+
this.#focusAfter(cmd, () => {
|
|
18901
|
+
const chatId = qwenworkChatIdOfSession(sid, readState(sid)?.cwd ?? "");
|
|
18902
|
+
if (!chatId) log(`\u6307\u4EE4 ${cmd.instructionId} \u5343\u95EE\u529E\u516C\u4F1A\u8BDD ${sid.slice(0, 8)} \u53CD\u67E5\u4E0D\u5230\u4EFB\u52A1\u53F7\uFF0C\u4E0D\u5207`);
|
|
18903
|
+
return chatId ? { kind: "qwenwork", chatId } : null;
|
|
18904
|
+
});
|
|
18539
18905
|
}
|
|
18540
18906
|
/** 扣子:设备回复写进 App 里的会话。started(mode=window);确定没发 → rejected + 原因;可能已发 → failed/window(🔴 不重发) */
|
|
18541
18907
|
async #toCoze(cmd, sid) {
|
|
@@ -18609,6 +18975,30 @@ var Daemon = class {
|
|
|
18609
18975
|
}
|
|
18610
18976
|
log(`\u6307\u4EE4 ${cmd.instructionId} \u5DF2\u5728\u5343\u95EE\u529E\u516C\u91CC\u5EFA\u4EFB\u52A1 ${r.chatId.slice(0, 8)}${r.sessionId ? ` \u4F1A\u8BDD ${r.sessionId.slice(0, 12)}` : ""} len=${cmd.text.length}`);
|
|
18611
18977
|
this.#report(cmd, r.sessionId, "started", "window");
|
|
18978
|
+
const created = r;
|
|
18979
|
+
this.#focusAfter(cmd, () => ({ kind: "qwenwork", chatId: created.chatId, ...created.subChatId ? { subChatId: created.subChatId } : {} }));
|
|
18980
|
+
}
|
|
18981
|
+
/**
|
|
18982
|
+
* 设备回复 / 新建任务交出去之后切到该会话(spec 2026-10-13 focus-session):只在这里(设备指令的分发路径)调,本机用户自己发起的不会走到。
|
|
18983
|
+
* 深链开关没开 / 平台不支持时不做反查等准备工作;任务总结不切。不阻塞回报、永不抛
|
|
18984
|
+
*/
|
|
18985
|
+
#focusAfter(cmd, target) {
|
|
18986
|
+
if (isSummary(cmd)) return;
|
|
18987
|
+
try {
|
|
18988
|
+
if (!this.#deps.focuser.remoteEnabled()) return;
|
|
18989
|
+
} catch {
|
|
18990
|
+
return;
|
|
18991
|
+
}
|
|
18992
|
+
void Promise.resolve().then(target).then((t) => t ? this.#deps.focuser.focus(cmd.instructionId, t) : null).catch(() => {
|
|
18993
|
+
});
|
|
18994
|
+
}
|
|
18995
|
+
/** opencode 这类 App 内切会话(不激活):开关开着且用户没在用电脑 */
|
|
18996
|
+
#focusInApp(cmd) {
|
|
18997
|
+
try {
|
|
18998
|
+
return this.#deps.focuser.inAppReady(cmd.instructionId);
|
|
18999
|
+
} catch {
|
|
19000
|
+
return false;
|
|
19001
|
+
}
|
|
18612
19002
|
}
|
|
18613
19003
|
/** 回报一条指令的处理结果。首发 2 秒超时,失败按退避重发结果(ResultReporter,不重新执行);seen 去重命中的不会走到这里 */
|
|
18614
19004
|
/** 指令结果回报:失败按退避重发(只重发结果、不重新执行),同一条指令只留最新一份 */
|
|
@@ -18627,6 +19017,12 @@ var Daemon = class {
|
|
|
18627
19017
|
at: String(this.#deps.now())
|
|
18628
19018
|
};
|
|
18629
19019
|
if (status === "done") payload.output = clipOutput(output ?? "");
|
|
19020
|
+
if (cmd.tool === "codex" && cmd.sessionId && sessionId && (status === "started" || status === "queued"))
|
|
19021
|
+
this.#focusAfter(cmd, async () => {
|
|
19022
|
+
if (await this.#deps.codexInApp(sessionId)) return { kind: "codex", threadId: sessionId };
|
|
19023
|
+
log(`\u6307\u4EE4 ${cmd.instructionId} Codex \u7EBF\u7A0B ${sessionId.slice(0, 8)} \u4E0D\u662F ChatGPT App \u91CC\u7684\uFF0C\u4E0D\u5207`);
|
|
19024
|
+
return null;
|
|
19025
|
+
});
|
|
18630
19026
|
if (isSummary(cmd) && status !== "started" && status !== "queued") this.#ptyWatch.drop(cmd.instructionId);
|
|
18631
19027
|
if (isSummary(cmd) && (status === "started" || status === "queued") && mode === "window" && SUMMARY_TRANSCRIPT_TOOLS.has(cmd.tool) && !this.#ptyWatch.has(cmd.instructionId))
|
|
18632
19028
|
this.#summaryWatch(cmd, sessionId);
|
|
@@ -22356,7 +22752,7 @@ async function runWorkbuddySetup(opts = {}) {
|
|
|
22356
22752
|
}
|
|
22357
22753
|
|
|
22358
22754
|
// src/cli.ts
|
|
22359
|
-
var
|
|
22755
|
+
var USAGE2 = `deskbuddy \u2014\u2014 \u628A AI \u7F16\u7801\u667A\u80FD\u4F53\u6BCF\u4E2A\u4F1A\u8BDD\u7A97\u53E3\u7684\u72B6\u6001\u540C\u6B65\u5230\u684C\u9762\u786C\u4EF6\u5C4F\u5E55
|
|
22360
22756
|
|
|
22361
22757
|
\u7528\u6CD5:
|
|
22362
22758
|
deskbuddy install --code <\u5B89\u88C5\u7801> \u4E00\u952E\u63A5\u5165\uFF1A\u7528 App\u300C\u6DFB\u52A0\u7535\u8111\u300D\u7ED9\u7684\u5B89\u88C5\u7801\u7ED1\u5B9A\u8D26\u53F7\uFF0C
|
|
@@ -22371,6 +22767,9 @@ var USAGE = `deskbuddy \u2014\u2014 \u628A AI \u7F16\u7801\u667A\u80FD\u4F53\u6B
|
|
|
22371
22767
|
deskbuddy status \u770B\u91C7\u96C6\u5230\u4E86\u4EC0\u4E48\u3001\u4E0A\u62A5\u4E86\u54EA\u4E9B\u5B57\u6BB5
|
|
22372
22768
|
deskbuddy logout [--yes] \u79FB\u9664 hook \u5E76\u6E05\u9664\u672C\u673A\u6570\u636E
|
|
22373
22769
|
deskbuddy config set transcript on|off \u4F1A\u8BDD\u5BF9\u8BDD\u540C\u6B65\u7684\u672C\u5730\u5F00\u5173\uFF08\u9ED8\u8BA4 on\uFF1Boff \u540E\u4E0D\u4E0A\u4F20\u4EFB\u4F55\u5BF9\u8BDD\u6B63\u6587\uFF09
|
|
22770
|
+
deskbuddy config set <\u5F00\u5173> true|false \u8BBE\u5907\u56DE\u590D / \u65B0\u5EFA\u4EFB\u52A1\u540E\u5207\u5230\u8BE5\u4F1A\u8BDD\uFF1AfocusSessionOnRemote\uFF08\u6DF1\u94FE\u628A
|
|
22771
|
+
App \u8C03\u5230\u524D\u53F0\uFF0C\u9ED8\u8BA4\u5173\uFF09\u3001focusSessionInApp\uFF08opencode \u5185\u5207\u6362\uFF0C\u9ED8\u8BA4\u5F00\uFF09\u3001
|
|
22772
|
+
focusSessionOnRemoteWindows\uFF08Windows \u4E5F\u7528\u6DF1\u94FE\uFF0C\u9ED8\u8BA4\u5173\uFF09
|
|
22374
22773
|
deskbuddy workbuddy-setup [--yes] \u5B89\u88C5 WorkBuddy \u7684\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08CodeBuddy Code\uFF09\uFF0C\u88C5\u4E86\u624D\u80FD\u5728
|
|
22375
22774
|
\u8BBE\u5907\u4E0A\u65B0\u5EFA / \u56DE\u590D WorkBuddy \u4EFB\u52A1\uFF08--yes \u4E0D\u5F39\u6846\u76F4\u63A5\u88C5\uFF09
|
|
22376
22775
|
deskbuddy wrap <tool> [\u53C2\u6570\u2026] \uFF08\u57AB\u7247\u5185\u90E8\u7528\uFF09\u7ECF DeskBuddy \u6253\u5F00\u667A\u80FD\u4F53\u7A97\u53E3\uFF0C
|
|
@@ -22395,7 +22794,7 @@ async function main(argv) {
|
|
|
22395
22794
|
case "-h":
|
|
22396
22795
|
case "--help":
|
|
22397
22796
|
case "help":
|
|
22398
|
-
process.stdout.write(
|
|
22797
|
+
process.stdout.write(USAGE2);
|
|
22399
22798
|
return 0;
|
|
22400
22799
|
case "-v":
|
|
22401
22800
|
case "--version":
|
|
@@ -22426,7 +22825,7 @@ async function main(argv) {
|
|
|
22426
22825
|
default:
|
|
22427
22826
|
process.stderr.write(`\u672A\u77E5\u547D\u4EE4\uFF1A${cmd}
|
|
22428
22827
|
|
|
22429
|
-
${
|
|
22828
|
+
${USAGE2}`);
|
|
22430
22829
|
return 1;
|
|
22431
22830
|
}
|
|
22432
22831
|
}
|
package/dist/ocTranslate.mjs
CHANGED
|
@@ -134,11 +134,27 @@ export const OC_PERMISSION_REPLY_MIN = '1.18.32'
|
|
|
134
134
|
* 版本读不出 / 格式不对 / 低于已验证版本 → false
|
|
135
135
|
*/
|
|
136
136
|
export function supportsPermissionReply(version) {
|
|
137
|
+
return ocVersionAtLeast(version, OC_PERMISSION_REPLY_MIN)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `POST /tui/select-session`(事件 tui.session.select,让终端界面切到某个会话)最早出现的版本:
|
|
142
|
+
* 逐版核对 npm 包 @opencode-ai/sdk 的生成代码,1.0.224 还没有、1.1.1 起有(spec 2026-10-13 focus-session §1)
|
|
143
|
+
*/
|
|
144
|
+
export const OC_SELECT_SESSION_MIN = '1.1.1'
|
|
145
|
+
|
|
146
|
+
/** 这个 opencode 版本有没有 /tui/select-session。读不出 / 格式不对 / 低于下限 → false(不认识的路由可能被转发到 app.opencode.ai,不能试探着打) */
|
|
147
|
+
export function supportsSelectSession(version) {
|
|
148
|
+
return ocVersionAtLeast(version, OC_SELECT_SESSION_MIN)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** version ≥ min(只比 主.次.修订);读不出 → false */
|
|
152
|
+
export function ocVersionAtLeast(version, min) {
|
|
137
153
|
const m = typeof version === 'string' ? version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/) : null
|
|
138
154
|
if (!m) return false
|
|
139
|
-
const
|
|
155
|
+
const lo = String(min).split('.').map(Number)
|
|
140
156
|
const v = [Number(m[1]), Number(m[2]), Number(m[3])]
|
|
141
|
-
for (let i = 0; i < 3; i++) if (v[i] !==
|
|
157
|
+
for (let i = 0; i < 3; i++) if (v[i] !== lo[i]) return v[i] > lo[i]
|
|
142
158
|
return true
|
|
143
159
|
}
|
|
144
160
|
|
package/dist/opencode.mjs
CHANGED
|
@@ -25,10 +25,10 @@ import { createHash, randomBytes } from 'node:crypto'
|
|
|
25
25
|
import { appendFileSync, chmodSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
26
26
|
import { dirname, join } from 'node:path'
|
|
27
27
|
import { fileURLToPath } from 'node:url'
|
|
28
|
-
import { alwaysSuggestions, askToCc, isPlainQuestion, ocReplyOf, questionToCc, rememberTitle, supportsPermissionReply, toCcTool } from './ocTranslate.mjs'
|
|
28
|
+
import { alwaysSuggestions, askToCc, isPlainQuestion, ocReplyOf, questionToCc, rememberTitle, supportsPermissionReply, supportsSelectSession, toCcTool } from './ocTranslate.mjs'
|
|
29
29
|
|
|
30
30
|
// 翻译函数搬到了 ocTranslate.mjs(与 DumateWatch 共用),这里转出去保持原来的导出
|
|
31
|
-
export { alwaysSuggestions, askToCc, isPlainQuestion, ocReplyOf, questionToCc, rememberTitle, supportsPermissionReply, toCcTool }
|
|
31
|
+
export { alwaysSuggestions, askToCc, isPlainQuestion, ocReplyOf, questionToCc, rememberTitle, supportsPermissionReply, supportsSelectSession, toCcTool }
|
|
32
32
|
|
|
33
33
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
34
34
|
const HOOK = join(HERE, 'hook.cjs')
|
|
@@ -322,9 +322,48 @@ export function startOcMailbox(opts) {
|
|
|
322
322
|
}
|
|
323
323
|
void tick()
|
|
324
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* 设备回复发进来之后在终端界面里切到这个会话(spec 2026-10-13 focus-session §1):不激活窗口、不抢焦点,只换 TUI 里显示的会话。
|
|
327
|
+
* 版本门控:先问一次 /global/health 的版本(进程内记住),低于 OC_SELECT_SESSION_MIN 或读不出就不打这个路由(不认识的路由可能被转发出去)
|
|
328
|
+
*/
|
|
329
|
+
let ocVersion = null // null = 还没问到
|
|
330
|
+
const http = () => client && (client._client || client.client)
|
|
331
|
+
const selectSession =
|
|
332
|
+
opts.selectSession ||
|
|
333
|
+
(async (sid, d) => {
|
|
334
|
+
const h = http()
|
|
335
|
+
if (ocVersion === null) {
|
|
336
|
+
try {
|
|
337
|
+
const r = h && typeof h.get === 'function' ? await withTimeout(h.get({ url: '/global/health' }), OC_STATUS_WAIT_MS) : null
|
|
338
|
+
// 超时 / 出错不记,下次再问
|
|
339
|
+
if (r && r !== 'timeout') ocVersion = r.data && typeof r.data.version === 'string' ? r.data.version : ''
|
|
340
|
+
} catch (_) {
|
|
341
|
+
/* 下次再问 */
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (!supportsSelectSession(ocVersion)) {
|
|
345
|
+
log(`opencode 版本 ${ocVersion || '读不出'}:没有 /tui/select-session,不切会话`)
|
|
346
|
+
return 'old_version'
|
|
347
|
+
}
|
|
348
|
+
if (!h || typeof h.post !== 'function') return 'failed'
|
|
349
|
+
try {
|
|
350
|
+
const r = await withTimeout(
|
|
351
|
+
h.post({ url: '/tui/select-session', query: { directory: d }, body: { sessionID: sid }, headers: { 'Content-Type': 'application/json' } }),
|
|
352
|
+
OC_PROMPT_WAIT_MS
|
|
353
|
+
)
|
|
354
|
+
if (r === 'timeout' || !sdkOk(r)) {
|
|
355
|
+
log(`切到会话 ${sid.slice(0, 12)} 失败:${r === 'timeout' ? '超时' : `status=${(r && r.response && r.response.status) || '?'}`}`)
|
|
356
|
+
return 'failed'
|
|
357
|
+
}
|
|
358
|
+
return 'ok'
|
|
359
|
+
} catch (e) {
|
|
360
|
+
log(`切到会话 ${sid.slice(0, 12)} 出错:${(e && e.message) || e}`)
|
|
361
|
+
return 'failed'
|
|
362
|
+
}
|
|
363
|
+
})
|
|
325
364
|
/** 会话 → 这个会话上一封信的处理链:同一会话的信串行发,前一封 promptAsync 返回了才发下一封(保证先后) */
|
|
326
365
|
const chains = new Map()
|
|
327
|
-
const enqueue = (sid, iid, text, at, replyWaitMs) => {
|
|
366
|
+
const enqueue = (sid, iid, text, at, replyWaitMs, focus) => {
|
|
328
367
|
const prev = chains.get(sid) || Promise.resolve()
|
|
329
368
|
const next = prev
|
|
330
369
|
.then(() => deliver(sid, text, at + OC_ACK_BUDGET_MS))
|
|
@@ -332,6 +371,8 @@ export function startOcMailbox(opts) {
|
|
|
332
371
|
(ack) => {
|
|
333
372
|
writeAck(iid, ack)
|
|
334
373
|
if (ack && ack.ok && replyWaitMs > 0) watchReply(sid, iid, at + replyWaitMs)
|
|
374
|
+
// 回执先写(不拖慢 daemon),再切会话;切不了不影响这一轮
|
|
375
|
+
if (ack && ack.ok && focus) void selectSession(sid, dirs.get(sid) || directory).catch(() => {})
|
|
335
376
|
},
|
|
336
377
|
() => writeAck(iid, { ok: false, reason: 'prompt_failed' })
|
|
337
378
|
)
|
|
@@ -389,10 +430,12 @@ export function startOcMailbox(opts) {
|
|
|
389
430
|
continue
|
|
390
431
|
}
|
|
391
432
|
const replyWaitMs = letter.summary === true && typeof letter.replyWaitMs === 'number' && letter.replyWaitMs > 0 ? Math.min(letter.replyWaitMs, OC_REPLY_MAX_MS) : 0
|
|
392
|
-
|
|
433
|
+
// 任务总结不切会话(daemon 也不会给总结带 focus,这里再兜一层)
|
|
434
|
+
const focus = letter.focus === true && letter.summary !== true
|
|
435
|
+
got.push({ sid, iid, at, text: typeof letter.text === 'string' ? letter.text : '', replyWaitMs, focus })
|
|
393
436
|
}
|
|
394
437
|
got.sort((a, b) => a.at - b.at)
|
|
395
|
-
for (const x of got) enqueue(x.sid, x.iid, x.text, x.at, x.replyWaitMs)
|
|
438
|
+
for (const x of got) enqueue(x.sid, x.iid, x.text, x.at, x.replyWaitMs, x.focus)
|
|
396
439
|
}
|
|
397
440
|
let t1 = null
|
|
398
441
|
let t2 = null
|
package/hook/hook-debug.log
CHANGED
|
@@ -536,3 +536,15 @@
|
|
|
536
536
|
2026-10-13T08:46:16.267Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
|
|
537
537
|
2026-10-13T08:46:16.267Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
538
538
|
2026-10-13T08:46:21.157Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
|
|
539
|
+
2026-10-13T09:14:17.159Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
|
|
540
|
+
2026-10-13T09:14:22.181Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
|
|
541
|
+
2026-10-13T09:14:22.181Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
542
|
+
2026-10-13T09:14:22.290Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
|
|
543
|
+
2026-10-13T09:14:22.291Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
544
|
+
2026-10-13T09:14:25.500Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
|
|
545
|
+
2026-10-13T10:34:12.495Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
|
|
546
|
+
2026-10-13T10:34:17.855Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
|
|
547
|
+
2026-10-13T10:34:17.856Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
548
|
+
2026-10-13T10:34:18.075Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
|
|
549
|
+
2026-10-13T10:34:18.075Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
|
|
550
|
+
2026-10-13T10:34:22.944Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
|
package/hook/ocTranslate.d.mts
CHANGED
|
@@ -7,3 +7,6 @@ export function questionToCc(props: unknown): { tool_name: string; tool_input: {
|
|
|
7
7
|
export function rememberTitle(titles: Map<string, string>, info: unknown): void
|
|
8
8
|
export function ocReplyOf(out: unknown): { reply: 'once' | 'always' | 'reject'; message?: string } | null
|
|
9
9
|
export function supportsPermissionReply(version: unknown): boolean
|
|
10
|
+
export const OC_SELECT_SESSION_MIN: string
|
|
11
|
+
export function supportsSelectSession(version: unknown): boolean
|
|
12
|
+
export function ocVersionAtLeast(version: unknown, min: string): boolean
|
package/hook/ocTranslate.mjs
CHANGED
|
@@ -134,11 +134,27 @@ export const OC_PERMISSION_REPLY_MIN = '1.18.32'
|
|
|
134
134
|
* 版本读不出 / 格式不对 / 低于已验证版本 → false
|
|
135
135
|
*/
|
|
136
136
|
export function supportsPermissionReply(version) {
|
|
137
|
+
return ocVersionAtLeast(version, OC_PERMISSION_REPLY_MIN)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `POST /tui/select-session`(事件 tui.session.select,让终端界面切到某个会话)最早出现的版本:
|
|
142
|
+
* 逐版核对 npm 包 @opencode-ai/sdk 的生成代码,1.0.224 还没有、1.1.1 起有(spec 2026-10-13 focus-session §1)
|
|
143
|
+
*/
|
|
144
|
+
export const OC_SELECT_SESSION_MIN = '1.1.1'
|
|
145
|
+
|
|
146
|
+
/** 这个 opencode 版本有没有 /tui/select-session。读不出 / 格式不对 / 低于下限 → false(不认识的路由可能被转发到 app.opencode.ai,不能试探着打) */
|
|
147
|
+
export function supportsSelectSession(version) {
|
|
148
|
+
return ocVersionAtLeast(version, OC_SELECT_SESSION_MIN)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** version ≥ min(只比 主.次.修订);读不出 → false */
|
|
152
|
+
export function ocVersionAtLeast(version, min) {
|
|
137
153
|
const m = typeof version === 'string' ? version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/) : null
|
|
138
154
|
if (!m) return false
|
|
139
|
-
const
|
|
155
|
+
const lo = String(min).split('.').map(Number)
|
|
140
156
|
const v = [Number(m[1]), Number(m[2]), Number(m[3])]
|
|
141
|
-
for (let i = 0; i < 3; i++) if (v[i] !==
|
|
157
|
+
for (let i = 0; i < 3; i++) if (v[i] !== lo[i]) return v[i] > lo[i]
|
|
142
158
|
return true
|
|
143
159
|
}
|
|
144
160
|
|
package/hook/opencode.d.mts
CHANGED
|
@@ -14,6 +14,7 @@ export function questionToCc(props: unknown): { tool_name: string; tool_input: {
|
|
|
14
14
|
export function isPlainQuestion(props: unknown, callTool: unknown): boolean
|
|
15
15
|
export function ocReplyOf(out: unknown): { reply: 'once' | 'always' | 'reject'; message?: string } | null
|
|
16
16
|
export function supportsPermissionReply(version: unknown): boolean
|
|
17
|
+
export function supportsSelectSession(version: unknown): boolean
|
|
17
18
|
export const OC_STALE_LETTER_MS: number
|
|
18
19
|
export const OC_LETTER_RE: RegExp
|
|
19
20
|
export const OC_MAILBOX_KEY: symbol
|
|
@@ -29,7 +30,17 @@ export interface OcMailboxHandle {
|
|
|
29
30
|
pid: number
|
|
30
31
|
token: string
|
|
31
32
|
}
|
|
32
|
-
export function startOcMailbox(opts: {
|
|
33
|
+
export function startOcMailbox(opts: {
|
|
34
|
+
client: unknown
|
|
35
|
+
directory?: string
|
|
36
|
+
home?: string
|
|
37
|
+
pid?: number
|
|
38
|
+
now?: () => number
|
|
39
|
+
log?: (m: string) => void
|
|
40
|
+
timers?: boolean
|
|
41
|
+
/** 投递成功后在终端界面里切到这个会话(测试注入;缺省先问 /global/health 的版本,够版本才打 /tui/select-session) */
|
|
42
|
+
selectSession?: (sessionId: string, directory: string) => Promise<'ok' | 'old_version' | 'failed'>
|
|
43
|
+
}): OcMailboxHandle
|
|
33
44
|
export function ensureOcMailbox(ctx: unknown, g?: Record<symbol, unknown>, log?: (m: string) => void): OcMailboxHandle | null
|
|
34
45
|
export const OC_ACK_BUDGET_MS: number
|
|
35
46
|
export function isTuiProcess(argv?: string[]): boolean
|
package/hook/opencode.mjs
CHANGED
|
@@ -25,10 +25,10 @@ import { createHash, randomBytes } from 'node:crypto'
|
|
|
25
25
|
import { appendFileSync, chmodSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
26
26
|
import { dirname, join } from 'node:path'
|
|
27
27
|
import { fileURLToPath } from 'node:url'
|
|
28
|
-
import { alwaysSuggestions, askToCc, isPlainQuestion, ocReplyOf, questionToCc, rememberTitle, supportsPermissionReply, toCcTool } from './ocTranslate.mjs'
|
|
28
|
+
import { alwaysSuggestions, askToCc, isPlainQuestion, ocReplyOf, questionToCc, rememberTitle, supportsPermissionReply, supportsSelectSession, toCcTool } from './ocTranslate.mjs'
|
|
29
29
|
|
|
30
30
|
// 翻译函数搬到了 ocTranslate.mjs(与 DumateWatch 共用),这里转出去保持原来的导出
|
|
31
|
-
export { alwaysSuggestions, askToCc, isPlainQuestion, ocReplyOf, questionToCc, rememberTitle, supportsPermissionReply, toCcTool }
|
|
31
|
+
export { alwaysSuggestions, askToCc, isPlainQuestion, ocReplyOf, questionToCc, rememberTitle, supportsPermissionReply, supportsSelectSession, toCcTool }
|
|
32
32
|
|
|
33
33
|
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
34
34
|
const HOOK = join(HERE, 'hook.cjs')
|
|
@@ -322,9 +322,48 @@ export function startOcMailbox(opts) {
|
|
|
322
322
|
}
|
|
323
323
|
void tick()
|
|
324
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* 设备回复发进来之后在终端界面里切到这个会话(spec 2026-10-13 focus-session §1):不激活窗口、不抢焦点,只换 TUI 里显示的会话。
|
|
327
|
+
* 版本门控:先问一次 /global/health 的版本(进程内记住),低于 OC_SELECT_SESSION_MIN 或读不出就不打这个路由(不认识的路由可能被转发出去)
|
|
328
|
+
*/
|
|
329
|
+
let ocVersion = null // null = 还没问到
|
|
330
|
+
const http = () => client && (client._client || client.client)
|
|
331
|
+
const selectSession =
|
|
332
|
+
opts.selectSession ||
|
|
333
|
+
(async (sid, d) => {
|
|
334
|
+
const h = http()
|
|
335
|
+
if (ocVersion === null) {
|
|
336
|
+
try {
|
|
337
|
+
const r = h && typeof h.get === 'function' ? await withTimeout(h.get({ url: '/global/health' }), OC_STATUS_WAIT_MS) : null
|
|
338
|
+
// 超时 / 出错不记,下次再问
|
|
339
|
+
if (r && r !== 'timeout') ocVersion = r.data && typeof r.data.version === 'string' ? r.data.version : ''
|
|
340
|
+
} catch (_) {
|
|
341
|
+
/* 下次再问 */
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (!supportsSelectSession(ocVersion)) {
|
|
345
|
+
log(`opencode 版本 ${ocVersion || '读不出'}:没有 /tui/select-session,不切会话`)
|
|
346
|
+
return 'old_version'
|
|
347
|
+
}
|
|
348
|
+
if (!h || typeof h.post !== 'function') return 'failed'
|
|
349
|
+
try {
|
|
350
|
+
const r = await withTimeout(
|
|
351
|
+
h.post({ url: '/tui/select-session', query: { directory: d }, body: { sessionID: sid }, headers: { 'Content-Type': 'application/json' } }),
|
|
352
|
+
OC_PROMPT_WAIT_MS
|
|
353
|
+
)
|
|
354
|
+
if (r === 'timeout' || !sdkOk(r)) {
|
|
355
|
+
log(`切到会话 ${sid.slice(0, 12)} 失败:${r === 'timeout' ? '超时' : `status=${(r && r.response && r.response.status) || '?'}`}`)
|
|
356
|
+
return 'failed'
|
|
357
|
+
}
|
|
358
|
+
return 'ok'
|
|
359
|
+
} catch (e) {
|
|
360
|
+
log(`切到会话 ${sid.slice(0, 12)} 出错:${(e && e.message) || e}`)
|
|
361
|
+
return 'failed'
|
|
362
|
+
}
|
|
363
|
+
})
|
|
325
364
|
/** 会话 → 这个会话上一封信的处理链:同一会话的信串行发,前一封 promptAsync 返回了才发下一封(保证先后) */
|
|
326
365
|
const chains = new Map()
|
|
327
|
-
const enqueue = (sid, iid, text, at, replyWaitMs) => {
|
|
366
|
+
const enqueue = (sid, iid, text, at, replyWaitMs, focus) => {
|
|
328
367
|
const prev = chains.get(sid) || Promise.resolve()
|
|
329
368
|
const next = prev
|
|
330
369
|
.then(() => deliver(sid, text, at + OC_ACK_BUDGET_MS))
|
|
@@ -332,6 +371,8 @@ export function startOcMailbox(opts) {
|
|
|
332
371
|
(ack) => {
|
|
333
372
|
writeAck(iid, ack)
|
|
334
373
|
if (ack && ack.ok && replyWaitMs > 0) watchReply(sid, iid, at + replyWaitMs)
|
|
374
|
+
// 回执先写(不拖慢 daemon),再切会话;切不了不影响这一轮
|
|
375
|
+
if (ack && ack.ok && focus) void selectSession(sid, dirs.get(sid) || directory).catch(() => {})
|
|
335
376
|
},
|
|
336
377
|
() => writeAck(iid, { ok: false, reason: 'prompt_failed' })
|
|
337
378
|
)
|
|
@@ -389,10 +430,12 @@ export function startOcMailbox(opts) {
|
|
|
389
430
|
continue
|
|
390
431
|
}
|
|
391
432
|
const replyWaitMs = letter.summary === true && typeof letter.replyWaitMs === 'number' && letter.replyWaitMs > 0 ? Math.min(letter.replyWaitMs, OC_REPLY_MAX_MS) : 0
|
|
392
|
-
|
|
433
|
+
// 任务总结不切会话(daemon 也不会给总结带 focus,这里再兜一层)
|
|
434
|
+
const focus = letter.focus === true && letter.summary !== true
|
|
435
|
+
got.push({ sid, iid, at, text: typeof letter.text === 'string' ? letter.text : '', replyWaitMs, focus })
|
|
393
436
|
}
|
|
394
437
|
got.sort((a, b) => a.at - b.at)
|
|
395
|
-
for (const x of got) enqueue(x.sid, x.iid, x.text, x.at, x.replyWaitMs)
|
|
438
|
+
for (const x of got) enqueue(x.sid, x.iid, x.text, x.at, x.replyWaitMs, x.focus)
|
|
396
439
|
}
|
|
397
440
|
let t1 = null
|
|
398
441
|
let t2 = null
|