deskbuddy 0.2.0-beta.17 → 0.2.0-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -731,7 +731,13 @@ function winNormalizedArgs(name, cmdline) {
731
731
  }
732
732
  return argv.map((a) => a.replace(/\s+/g, "_").replace(/\\/g, "/")).join(" ");
733
733
  }
734
- var WIN_PS_SCRIPT = "[Console]::OutputEncoding=[Text.Encoding]::UTF8;Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine | ConvertTo-Json -Compress";
734
+ var WIN_PS_SCRIPT = "[Console]::OutputEncoding=[Text.Encoding]::UTF8;Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine,CreationDate | ConvertTo-Json -Compress";
735
+ function winCreationMs(v) {
736
+ if (typeof v !== "string" || !v) return void 0;
737
+ const m = /^\/?Date\((\d+)/.exec(v);
738
+ const ms = m ? Number(m[1]) : Date.parse(v);
739
+ return Number.isFinite(ms) && ms > 0 ? ms : void 0;
740
+ }
735
741
  function parseWinProcJson(json) {
736
742
  let d;
737
743
  try {
@@ -746,12 +752,14 @@ function parseWinProcJson(json) {
746
752
  if (!Number.isInteger(pid) || pid <= 0) continue;
747
753
  const rawName = typeof r.Name === "string" ? r.Name : "";
748
754
  const cmdline = typeof r.CommandLine === "string" ? r.CommandLine.replace(/[\r\n]+/g, " ") : "";
755
+ const start = winCreationMs(r.CreationDate);
749
756
  out.push({
750
757
  pid,
751
758
  ppid: Number(r.ParentProcessId) || 0,
752
759
  name: winEffectiveName(rawName, cmdline),
753
760
  args: winNormalizedArgs(rawName, cmdline),
754
- cmdline
761
+ cmdline,
762
+ ...start !== void 0 ? { start } : {}
755
763
  });
756
764
  }
757
765
  return out;
@@ -1499,10 +1507,27 @@ import { existsSync as existsSync8 } from "fs";
1499
1507
  import { dirname as dirname7 } from "path";
1500
1508
 
1501
1509
  // src/daemon/locate.ts
1502
- import { execFileSync as execFileSync5 } from "child_process";
1503
- import { accessSync, constants, existsSync as existsSync7, lstatSync, readFileSync as readFileSync6, realpathSync, statSync as statSync2 } from "fs";
1510
+ import { execFile as execFile3, execFileSync as execFileSync5 } from "child_process";
1511
+ import { accessSync, closeSync as closeSync2, constants, existsSync as existsSync7, lstatSync, openSync as openSync2, readFileSync as readFileSync6, readSync, realpathSync, statSync as statSync2 } from "fs";
1504
1512
  import { homedir as homedir3 } from "os";
1505
1513
  import { delimiter, dirname as dirname6, join as join7, win32 as win322 } from "path";
1514
+
1515
+ // src/core/agentEnv.ts
1516
+ var BUN_HIJACK_ENV = ["BUN_BE_BUN", "BUN_OPTIONS"];
1517
+ function scrubAgentEnv(env) {
1518
+ for (const k of Object.keys(env)) {
1519
+ if (BUN_HIJACK_ENV.includes(k.toUpperCase())) delete env[k];
1520
+ }
1521
+ return env;
1522
+ }
1523
+ var AGENT_SPAWNING_COMMANDS = ["daemon", "wrap"];
1524
+ function scrubForCommand(cmd, env = process.env) {
1525
+ if (!cmd || !AGENT_SPAWNING_COMMANDS.includes(cmd)) return false;
1526
+ scrubAgentEnv(env);
1527
+ return true;
1528
+ }
1529
+
1530
+ // src/daemon/locate.ts
1506
1531
  function qoderWorker(d, worker, appBin) {
1507
1532
  if (d.isExecutable(appBin)) return { file: appBin, prefixArgs: [worker], env: { ELECTRON_RUN_AS_NODE: "1" } };
1508
1533
  return { file: d.nodePath, prefixArgs: [worker] };
@@ -1541,8 +1566,124 @@ var defaultLocateDeps = () => ({
1541
1566
  },
1542
1567
  // 🔴 daemon 自己也不能找到垫片:远程任务要的是真程序(窗口接管 spec 2026-09-30 §3.1)
1543
1568
  skipDirs: [join7(deskbuddyHome(), "bin")],
1544
- winAppHints: (product, exe) => winInstallDirs(product, exe)
1569
+ winAppHints: (product, exe) => winInstallDirs(product, exe),
1570
+ isPe: (p) => {
1571
+ let fd = null;
1572
+ try {
1573
+ fd = openSync2(p, "r");
1574
+ const b = Buffer.alloc(2);
1575
+ return readSync(fd, b, 0, 2, 0) === 2 && b.toString("latin1") === "MZ";
1576
+ } catch {
1577
+ return false;
1578
+ } finally {
1579
+ if (fd !== null) closeSync2(fd);
1580
+ }
1581
+ },
1582
+ versionOf: (loc) => probeVersion(loc)
1583
+ });
1584
+ var PROBE_TIMEOUT_MS = 3e3;
1585
+ var PROBE_SHORT_TTL_MS = 5 * 60 * 1e3;
1586
+ function createVersionProbe(d) {
1587
+ const cache2 = /* @__PURE__ */ new Map();
1588
+ const keyOf = (loc) => {
1589
+ let key = [loc.file, ...loc.prefixArgs].join("\0");
1590
+ for (const p of [loc.file, ...loc.prefixArgs.slice(0, 1)]) {
1591
+ const st = d.stat(p);
1592
+ if (st) key += `\0${st.size}:${st.mtimeMs}`;
1593
+ }
1594
+ return key;
1595
+ };
1596
+ const cached = (key) => {
1597
+ const hit = cache2.get(key);
1598
+ if (!hit) return null;
1599
+ if (hit.until > d.now()) return hit;
1600
+ cache2.delete(key);
1601
+ return null;
1602
+ };
1603
+ const record = (key, r) => {
1604
+ if (r.kind === "spawn_error") return null;
1605
+ if (r.kind === "timeout") {
1606
+ cache2.set(key, { out: null, until: d.now() + PROBE_SHORT_TTL_MS });
1607
+ return null;
1608
+ }
1609
+ const ok = /Claude Code/i.test(r.out);
1610
+ cache2.set(key, { out: r.out, until: ok ? Number.POSITIVE_INFINITY : d.now() + PROBE_SHORT_TTL_MS });
1611
+ return r.out;
1612
+ };
1613
+ return {
1614
+ probe(loc) {
1615
+ const key = keyOf(loc);
1616
+ const hit = cached(key);
1617
+ if (hit) return hit.out;
1618
+ return record(key, d.runSync(loc));
1619
+ },
1620
+ async warm(loc) {
1621
+ const key = keyOf(loc);
1622
+ if (cached(key)) return;
1623
+ record(key, await d.runAsync(loc));
1624
+ }
1625
+ };
1626
+ }
1627
+ var probeEnv = (loc) => ({ ...scrubAgentEnv({ ...process.env }), ...loc.env ?? {} });
1628
+ function classifySyncError(e) {
1629
+ const err = e;
1630
+ if (err.code === "ETIMEDOUT" || err.signal) return { kind: "timeout" };
1631
+ if (typeof err.status === "number") return { kind: "done", out: `${err.stdout ?? ""}${err.stderr ?? ""}` };
1632
+ return { kind: "spawn_error" };
1633
+ }
1634
+ function classifyAsyncError(e, stdout7, stderr) {
1635
+ const err = e;
1636
+ if (typeof err.code === "string") return { kind: "spawn_error" };
1637
+ if (err.killed || err.signal) return { kind: "timeout" };
1638
+ return { kind: "done", out: `${stdout7}${stderr}` };
1639
+ }
1640
+ var defaultProbe = createVersionProbe({
1641
+ stat: (p) => {
1642
+ try {
1643
+ const st = statSync2(p);
1644
+ return { size: st.size, mtimeMs: st.mtimeMs };
1645
+ } catch {
1646
+ return null;
1647
+ }
1648
+ },
1649
+ runSync: (loc) => {
1650
+ try {
1651
+ const out = execFileSync5(loc.file, [...loc.prefixArgs, "--version"], {
1652
+ encoding: "utf8",
1653
+ stdio: ["ignore", "pipe", "pipe"],
1654
+ windowsHide: true,
1655
+ timeout: PROBE_TIMEOUT_MS,
1656
+ env: probeEnv(loc)
1657
+ });
1658
+ return { kind: "done", out };
1659
+ } catch (e) {
1660
+ return classifySyncError(e);
1661
+ }
1662
+ },
1663
+ runAsync: (loc) => new Promise((resolve10) => {
1664
+ try {
1665
+ execFile3(
1666
+ loc.file,
1667
+ [...loc.prefixArgs, "--version"],
1668
+ { encoding: "utf8", windowsHide: true, timeout: PROBE_TIMEOUT_MS, env: probeEnv(loc) },
1669
+ (err, stdout7, stderr) => resolve10(err ? classifyAsyncError(err, String(stdout7 ?? ""), String(stderr ?? "")) : { kind: "done", out: `${stdout7}${stderr}` })
1670
+ );
1671
+ } catch {
1672
+ resolve10({ kind: "spawn_error" });
1673
+ }
1674
+ }),
1675
+ now: () => Date.now()
1545
1676
  });
1677
+ function probeVersion(loc) {
1678
+ return defaultProbe.probe(loc);
1679
+ }
1680
+ async function warmClaudeProbe(d = defaultLocateDeps()) {
1681
+ if (!isWin(d)) return;
1682
+ try {
1683
+ for (const { loc } of winClaudeLaunches(d)) await defaultProbe.warm(loc);
1684
+ } catch {
1685
+ }
1686
+ }
1546
1687
  function installDirsFromProcs(procs, exe) {
1547
1688
  const out = [];
1548
1689
  for (const p of procs ?? []) {
@@ -1639,6 +1780,7 @@ function parseNpmCmdShim(shimPath, text) {
1639
1780
  if (!m) return null;
1640
1781
  return join7(dirname6(shimPath), m[1].replace(/^\\/, ""));
1641
1782
  }
1783
+ var JS_RUNTIME_EXE = /^(?:node|bun|bunx|deno)(?:\.exe)?$/i;
1642
1784
  function resolveWinLaunch(file, d) {
1643
1785
  const lower = file.toLowerCase();
1644
1786
  if (lower.endsWith(".exe") || lower.endsWith(".com")) return { file, prefixArgs: [] };
@@ -1647,6 +1789,7 @@ function resolveWinLaunch(file, d) {
1647
1789
  const target = text ? parseNpmCmdShim(file, text) : null;
1648
1790
  if (!target || !d.exists(target)) return null;
1649
1791
  if (/\.(?:cmd|bat)$/i.test(target)) return null;
1792
+ if (JS_RUNTIME_EXE.test(win322.basename(target.replace(/\//g, "\\")))) return null;
1650
1793
  return resolveWinLaunch(target, d);
1651
1794
  }
1652
1795
  if (/\.(?:c|m)?js$/.test(lower)) return { file: d.nodePath, prefixArgs: [file] };
@@ -1673,6 +1816,32 @@ function winClaudeExeCandidates(d) {
1673
1816
  ...d.appData ? [join7(d.appData, "npm", "node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe")] : []
1674
1817
  ];
1675
1818
  }
1819
+ function winClaudeLaunches(d) {
1820
+ const out = [];
1821
+ for (const p of winClaudeExeCandidates(d)) if (d.exists(p)) out.push({ src: p, loc: { file: p, prefixArgs: [] } });
1822
+ for (const f of whichAllIn("claude", d)) {
1823
+ const loc = resolveWinLaunch(f, d);
1824
+ if (loc) out.push({ src: f, loc });
1825
+ }
1826
+ return out;
1827
+ }
1828
+ function isClaudeCode(loc, d) {
1829
+ if (loc.prefixArgs.length === 0 && /\.exe$/i.test(loc.file) && d.isPe && !d.isPe(loc.file)) return false;
1830
+ const out = d.versionOf?.(loc);
1831
+ if (out === void 0 || out === null) return true;
1832
+ return /Claude Code/i.test(out);
1833
+ }
1834
+ function pickWinClaude(d, accept = () => true) {
1835
+ const tried = /* @__PURE__ */ new Set();
1836
+ for (const { src, loc } of winClaudeLaunches(d)) {
1837
+ if (!accept(src, loc)) continue;
1838
+ const key = [loc.file, ...loc.prefixArgs].join("\0").toLowerCase();
1839
+ if (tried.has(key)) continue;
1840
+ tried.add(key);
1841
+ if (isClaudeCode(loc, d)) return loc;
1842
+ }
1843
+ return null;
1844
+ }
1676
1845
  function locateWin(tool, d) {
1677
1846
  const plain = (name) => {
1678
1847
  for (const f of whichAllIn(name, d)) {
@@ -1683,8 +1852,7 @@ function locateWin(tool, d) {
1683
1852
  };
1684
1853
  switch (tool) {
1685
1854
  case "claude_code":
1686
- for (const p of winClaudeExeCandidates(d)) if (d.exists(p)) return { file: p, prefixArgs: [] };
1687
- return plain("claude");
1855
+ return pickWinClaude(d);
1688
1856
  case "codex":
1689
1857
  return plain("codex");
1690
1858
  case "workbuddy": {
@@ -1917,7 +2085,9 @@ var FALLBACK = {
1917
2085
  // Qoder 国内版(Qoder CN.app,配置 ~/.qoder-cn)
1918
2086
  qoder_cn: {
1919
2087
  format: "cc-hook",
1920
- processNames: ["Qoder CN"],
2088
+ // qoderclicn:终端里的命令行窗口(Bug 204)。认不出它的话会话没有进程号、interactive=false,
2089
+ // 打断补落与窗口授权都不管(2026-10-03 qoderclicn 1.1.59 实测)
2090
+ processNames: ["Qoder CN", "qoderclicn"],
1921
2091
  // 桌面端会话库里的标题(先是首条指令,后被换成 AI 摘要);命令行跑的会话不在库里,落到首条指令
1922
2092
  // (CLI 1.1.61 实测 transcript 里没有 ai-title,UserPromptSubmit 带 prompt)
1923
2093
  titleSources: ["qoder-session-title", "first-prompt", "git-branch", "project"],
@@ -3211,7 +3381,7 @@ var AutoEnabler = class {
3211
3381
  // src/daemon/index.ts
3212
3382
  import { randomBytes as randomBytes7 } from "crypto";
3213
3383
  import { existsSync as existsSync20, statSync as statSync14 } from "fs";
3214
- import { basename as basename9, dirname as dirname18, join as join36 } from "path";
3384
+ import { basename as basename9, dirname as dirname19, join as join36 } from "path";
3215
3385
  import WebSocket2 from "ws";
3216
3386
 
3217
3387
  // src/core/state.ts
@@ -3277,8 +3447,55 @@ function isAlive(pid, expectName) {
3277
3447
  return name != null && name.toLowerCase().includes(expectName.toLowerCase());
3278
3448
  }
3279
3449
 
3450
+ // src/core/codexServer.ts
3451
+ import { execFileSync as execFileSync8 } from "child_process";
3452
+ function isAppServerArgs(args, url) {
3453
+ return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
3454
+ }
3455
+ function processArgs(pid, platform = process.platform, procs, fresh = false) {
3456
+ if (platform === "win32") {
3457
+ const list = procs ? procs() : winProcs(Date.now(), { force: fresh });
3458
+ const p = (list ?? []).find((x) => x.pid === pid);
3459
+ return p?.args || null;
3460
+ }
3461
+ try {
3462
+ const out = execFileSync8("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
3463
+ encoding: "utf8",
3464
+ stdio: ["ignore", "pipe", "ignore"]
3465
+ });
3466
+ return out.trim() || null;
3467
+ } catch {
3468
+ return null;
3469
+ }
3470
+ }
3471
+ function isCodexManagedDaemonArgs(args) {
3472
+ const s2 = String(args || "").replace(/\\/g, "/");
3473
+ return /(?:^|\s)--managed-daemon(?:[=\s]|$)/.test(s2) || /\/app-server-daemon\//i.test(programOf(s2)) || /(?:^|\s)app-server\s+daemon(?:\s|$)/.test(s2);
3474
+ }
3475
+ var SCRIPT_HOST_RE = /^(?:node|bun|deno)(?:\.exe)?$/i;
3476
+ function baseName(p) {
3477
+ return p.replace(/\\/g, "/").split("/").pop().toLowerCase();
3478
+ }
3479
+ function progAt(all, i, re) {
3480
+ for (let j = i; j < Math.min(all.length, i + 8); j++) {
3481
+ const cand = all.slice(i, j + 1).join(" ");
3482
+ if (re.test(baseName(cand))) return { prog: cand, next: j + 1 };
3483
+ if (!/^(?:\/|[A-Za-z]:\/)/.test(all[i].replace(/\\/g, "/"))) break;
3484
+ }
3485
+ return null;
3486
+ }
3487
+ function programOf(args) {
3488
+ const all = String(args || "").trim().split(/\s+/).filter(Boolean);
3489
+ const h = progAt(all, 0, SCRIPT_HOST_RE);
3490
+ if (h) {
3491
+ const k = all.findIndex((a, n) => n >= h.next && !a.startsWith("-"));
3492
+ if (k >= 0) return all[k];
3493
+ }
3494
+ return all[0] || "";
3495
+ }
3496
+
3280
3497
  // src/core/ended.ts
3281
- import { closeSync as closeSync2, mkdirSync as mkdirSync12, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync14, readSync, realpathSync as realpathSync2, renameSync as renameSync8, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync11 } from "fs";
3498
+ import { closeSync as closeSync3, mkdirSync as mkdirSync12, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync14, readSync as readSync2, realpathSync as realpathSync2, renameSync as renameSync8, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync11 } from "fs";
3282
3499
  import { join as join16 } from "path";
3283
3500
  var ENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
3284
3501
  var SID_RE = /^[A-Za-z0-9_-]{1,64}$/;
@@ -3328,11 +3545,11 @@ function readChunk(file, fromEnd, max) {
3328
3545
  const size = statSync5(file).size;
3329
3546
  const len = Math.min(size, max);
3330
3547
  const buf = Buffer.alloc(len);
3331
- fd = openSync2(file, "r");
3332
- readSync(fd, buf, 0, len, fromEnd ? size - len : 0);
3548
+ fd = openSync3(file, "r");
3549
+ readSync2(fd, buf, 0, len, fromEnd ? size - len : 0);
3333
3550
  return buf.toString("utf8");
3334
3551
  } finally {
3335
- if (fd !== void 0) closeSync2(fd);
3552
+ if (fd !== void 0) closeSync3(fd);
3336
3553
  }
3337
3554
  }
3338
3555
  function cwdsIn(text) {
@@ -3446,6 +3663,13 @@ function isStaleNoPid(s2, now, pending) {
3446
3663
  if (s2.pid || !STALE_STATES.has(s2.state) || !(now - s2.updatedAt > STALE_NO_PID_MS)) return false;
3447
3664
  return !pending().has(s2.sessionId);
3448
3665
  }
3666
+ function dropManagedDaemonPid(s2, args = (pid) => processArgs(pid)) {
3667
+ if (s2.tool !== "codex" || !s2.pid || s2.pidChecked === true || !isCodexManagedDaemonArgs(args(s2.pid) ?? "")) return s2;
3668
+ const next = { ...s2 };
3669
+ delete next.pid;
3670
+ delete next.pidName;
3671
+ return next;
3672
+ }
3449
3673
  function readAliveStates(tool, opts = {}) {
3450
3674
  if (!existsSync16(stateDir())) return [];
3451
3675
  const now = opts.now ?? Date.now();
@@ -3466,6 +3690,7 @@ function readAliveStates(tool, opts = {}) {
3466
3690
  retireState(s2);
3467
3691
  continue;
3468
3692
  }
3693
+ s2 = dropManagedDaemonPid(s2, opts.args);
3469
3694
  if (isStaleNoPid(s2, now, pending)) {
3470
3695
  try {
3471
3696
  opts.onStale?.(s2);
@@ -3713,7 +3938,7 @@ function isExpired(cmd, now = Date.now()) {
3713
3938
  }
3714
3939
 
3715
3940
  // src/daemon/executor.ts
3716
- import { execFile as execFile3, execFileSync as execFileSync8 } from "child_process";
3941
+ import { execFile as execFile4, execFileSync as execFileSync9 } from "child_process";
3717
3942
  import { homedir as homedir6 } from "os";
3718
3943
  import { join as join19 } from "path";
3719
3944
  var EXEC_TIMEOUT_MS = 10 * 60 * 1e3;
@@ -3991,7 +4216,7 @@ function startCommand(plan, tool, limitMs = EXEC_TIMEOUT_MS) {
3991
4216
  };
3992
4217
  let timedOut = false;
3993
4218
  const done = new Promise((resolve10) => {
3994
- child = execFile3(
4219
+ child = execFile4(
3995
4220
  plan.file,
3996
4221
  plan.args,
3997
4222
  {
@@ -4039,7 +4264,7 @@ function startCommand(plan, tool, limitMs = EXEC_TIMEOUT_MS) {
4039
4264
  };
4040
4265
  }
4041
4266
  function mergeEnv(base, extra, platform = process.platform) {
4042
- const env = { ...base };
4267
+ const env = scrubAgentEnv({ ...base });
4043
4268
  for (const [k, v] of Object.entries(extra ?? {})) {
4044
4269
  if (platform === "win32") {
4045
4270
  for (const old of Object.keys(env)) if (old !== k && old.toUpperCase() === k.toUpperCase()) delete env[old];
@@ -4052,7 +4277,7 @@ function killTree(child) {
4052
4277
  if (!child) return;
4053
4278
  if (process.platform === "win32" && child.pid) {
4054
4279
  try {
4055
- execFileSync8("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
4280
+ execFileSync9("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
4056
4281
  return;
4057
4282
  } catch {
4058
4283
  }
@@ -4345,7 +4570,7 @@ function editInProject(toolName, toolInput, cwd, opts) {
4345
4570
  }
4346
4571
 
4347
4572
  // src/daemon/gate/question.ts
4348
- import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync6 } from "fs";
4573
+ import { closeSync as closeSync4, openSync as openSync4, readSync as readSync3, statSync as statSync6 } from "fs";
4349
4574
  var MAX_QUESTIONS = 4;
4350
4575
  var MAX_OPTIONS = 4;
4351
4576
  var HEADER_MAX = 12;
@@ -4455,11 +4680,11 @@ function lastAssistantText(transcriptPath) {
4455
4680
  const size = statSync6(transcriptPath).size;
4456
4681
  const len = Math.min(size, TAIL_BYTES);
4457
4682
  const buf = Buffer.alloc(len);
4458
- const fd = openSync3(transcriptPath, "r");
4683
+ const fd = openSync4(transcriptPath, "r");
4459
4684
  try {
4460
- readSync2(fd, buf, 0, len, size - len);
4685
+ readSync3(fd, buf, 0, len, size - len);
4461
4686
  } finally {
4462
- closeSync3(fd);
4687
+ closeSync4(fd);
4463
4688
  }
4464
4689
  text = buf.toString("utf8");
4465
4690
  } catch {
@@ -4575,14 +4800,14 @@ function pcAnswers(toolInput, rec) {
4575
4800
  }
4576
4801
 
4577
4802
  // src/daemon/gate/procs.ts
4578
- import { execFileSync as execFileSync9 } from "child_process";
4803
+ import { execFileSync as execFileSync10 } from "child_process";
4579
4804
  function listDescendants(rootPid) {
4580
4805
  if (process.platform === "win32") {
4581
4806
  return descendantsOf(rootPid, (winProcs() ?? []).map((p) => ({ pid: p.pid, ppid: p.ppid, args: p.cmdline })));
4582
4807
  }
4583
4808
  let out = "";
4584
4809
  try {
4585
- out = execFileSync9("ps", ["-A", "-ww", "-o", "pid=,ppid=,args="], {
4810
+ out = execFileSync10("ps", ["-A", "-ww", "-o", "pid=,ppid=,args="], {
4586
4811
  encoding: "utf8",
4587
4812
  stdio: ["ignore", "pipe", "ignore"],
4588
4813
  maxBuffer: 16 * 1024 * 1024
@@ -6241,7 +6466,7 @@ function sleep(ms) {
6241
6466
  }
6242
6467
 
6243
6468
  // src/daemon/workbuddyBridge.ts
6244
- import { closeSync as closeSync5, openSync as openSync5, readSync as readSync4, realpathSync as realpathSync4, statSync as statSync10 } from "fs";
6469
+ import { closeSync as closeSync6, openSync as openSync6, readSync as readSync5, realpathSync as realpathSync4, statSync as statSync10 } from "fs";
6245
6470
  import { dirname as dirname16, sep as sep3 } from "path";
6246
6471
 
6247
6472
  // src/core/wrapWindows.ts
@@ -6250,9 +6475,9 @@ import { tmpdir as tmpdir3 } from "os";
6250
6475
  import { join as join27 } from "path";
6251
6476
 
6252
6477
  // src/core/wake.ts
6253
- import { execFileSync as execFileSync10 } from "child_process";
6478
+ import { execFileSync as execFileSync11 } from "child_process";
6254
6479
  import { createHash as createHash3, createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
6255
- import { chmodSync as chmodSync6, closeSync as closeSync4, mkdirSync as mkdirSync19, openSync as openSync4, readdirSync as readdirSync6, readFileSync as readFileSync20, readSync as readSync3, renameSync as renameSync13, rmSync as rmSync9, statSync as statSync8, writeFileSync as writeFileSync17 } from "fs";
6480
+ import { chmodSync as chmodSync6, closeSync as closeSync5, mkdirSync as mkdirSync19, openSync as openSync5, readdirSync as readdirSync6, readFileSync as readFileSync20, readSync as readSync4, renameSync as renameSync13, rmSync as rmSync9, statSync as statSync8, writeFileSync as writeFileSync17 } from "fs";
6256
6481
  import { tmpdir as tmpdir2 } from "os";
6257
6482
  import { join as join26 } from "path";
6258
6483
  function wakeDir(home = deskbuddyHome()) {
@@ -6343,7 +6568,7 @@ function writeWakeSecret(hex, platform = process.platform) {
6343
6568
  writeFileSync17(tmp, hex, { encoding: "utf8", mode: 384 });
6344
6569
  if (platform === "win32") {
6345
6570
  try {
6346
- execFileSync10("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
6571
+ execFileSync11("icacls", [tmp, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
6347
6572
  } catch {
6348
6573
  renameSync13(tmp, file);
6349
6574
  return "\u7A97\u53E3\u5524\u9192\u5BC6\u94A5\u6587\u4EF6\u7684\u6743\u9650\u6CA1\u80FD\u6536\u7D27\uFF08icacls \u5931\u8D25\uFF09";
@@ -6357,7 +6582,7 @@ function writeWakeSecret(hex, platform = process.platform) {
6357
6582
  function restrictToUser(file, platform = process.platform) {
6358
6583
  if (platform !== "win32") return true;
6359
6584
  try {
6360
- execFileSync10("icacls", [file, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
6585
+ execFileSync11("icacls", [file, "/inheritance:r", "/grant:r", `${currentUserSid()}:F`], { stdio: "ignore", windowsHide: true });
6361
6586
  return true;
6362
6587
  } catch {
6363
6588
  return false;
@@ -6365,7 +6590,7 @@ function restrictToUser(file, platform = process.platform) {
6365
6590
  }
6366
6591
  function currentUserSid() {
6367
6592
  try {
6368
- const out = execFileSync10("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
6593
+ const out = execFileSync11("whoami", ["/user", "/fo", "csv", "/nh"], { encoding: "utf8", windowsHide: true });
6369
6594
  const m = /"(S-1-[0-9-]+)"/.exec(out);
6370
6595
  if (m) return `*${m[1]}`;
6371
6596
  } catch {
@@ -6403,8 +6628,8 @@ function analyzeTranscript(file) {
6403
6628
  const size = statSync8(file).size;
6404
6629
  const len = Math.min(size, TRANSCRIPT_TAIL2);
6405
6630
  const buf = Buffer.alloc(len);
6406
- fd = openSync4(file, "r");
6407
- readSync3(fd, buf, 0, len, size - len);
6631
+ fd = openSync5(file, "r");
6632
+ readSync4(fd, buf, 0, len, size - len);
6408
6633
  const lines2 = buf.toString("utf8").split("\n");
6409
6634
  let seg = false;
6410
6635
  let reply = null;
@@ -6445,7 +6670,7 @@ function analyzeTranscript(file) {
6445
6670
  } finally {
6446
6671
  if (fd !== void 0) {
6447
6672
  try {
6448
- closeSync4(fd);
6673
+ closeSync5(fd);
6449
6674
  } catch {
6450
6675
  }
6451
6676
  }
@@ -6820,6 +7045,12 @@ function parseAnswer(m) {
6820
7045
  const toolName = toolNameOf(a.toolName);
6821
7046
  if (!toolName) return null;
6822
7047
  if (a.trust !== true && a.prompt !== void 0) return null;
7048
+ if (a.login !== void 0) {
7049
+ if (a.login !== true || a.trust !== void 0 || a.questions !== void 0) return null;
7050
+ const fp = a.fingerprint === void 0 ? void 0 : str3(a.fingerprint, 200);
7051
+ if (fp === null) return null;
7052
+ return { instructionId, sessionId, expireAt, answer: { kind: "probe", toolName, login: true, ...fp ? { fingerprint: fp } : {} } };
7053
+ }
6823
7054
  if (a.trust === true) {
6824
7055
  if (a.fingerprint !== void 0 || a.questions !== void 0) return null;
6825
7056
  return { instructionId, sessionId, expireAt, answer: { kind: "probe", toolName, trust: true, ...hooks } };
@@ -7141,11 +7372,11 @@ function tail(path) {
7141
7372
  const size = statSync10(path).size;
7142
7373
  const len = Math.min(size, TAIL_BYTES2);
7143
7374
  const buf = Buffer.alloc(len);
7144
- const fd = openSync5(path, "r");
7375
+ const fd = openSync6(path, "r");
7145
7376
  try {
7146
- readSync4(fd, buf, 0, len, size - len);
7377
+ readSync5(fd, buf, 0, len, size - len);
7147
7378
  } finally {
7148
- closeSync5(fd);
7379
+ closeSync6(fd);
7149
7380
  }
7150
7381
  return buf.toString("utf8");
7151
7382
  } catch {
@@ -7291,6 +7522,16 @@ var WorkbuddyBridge = class {
7291
7522
  this.#prune();
7292
7523
  if (this.#seen.has(key)) return;
7293
7524
  this.#seen.set(key, this.#now());
7525
+ if (await this.#onLoginPage(windowId, rec, call2.callId, toolName, fingerprint2)) {
7526
+ this.#seen.delete(key);
7527
+ this.#d.log(`WorkBuddy\uFF1A\u4F1A\u8BDD ${sessionId.slice(0, 8)} \u7684 ${toolName} \u6388\u6743\u4E0D\u63A8\u8BBE\u5907\u3001\u64A4\u5361\uFF08\u9700\u8981\u767B\u5F55\uFF1A\u7A97\u53E3\u8FD8\u505C\u5728\u767B\u5F55\u9875\uFF09`);
7528
+ try {
7529
+ this.#d.loginBlocked?.(windowId, sessionId);
7530
+ } catch {
7531
+ }
7532
+ return;
7533
+ }
7534
+ if (this.#stopped) return;
7294
7535
  const old = this.#cards.get(sessionId);
7295
7536
  if (old) {
7296
7537
  this.#cards.delete(sessionId);
@@ -7322,6 +7563,23 @@ var WorkbuddyBridge = class {
7322
7563
  this.#cards.delete(sessionId);
7323
7564
  await this.#answer(card, reply);
7324
7565
  }
7566
+ /**
7567
+ * 窗口此刻还在登录页?只问认出过登录页的窗口;🔴 拿不准(通道不通、老包装层不认、超时)一律按不在 —— 宁可照常推卡,不误撤
7568
+ */
7569
+ async #onLoginPage(windowId, rec, callId, toolName, fingerprint2) {
7570
+ try {
7571
+ if (!this.#d.loginWindow?.(windowId)) return false;
7572
+ const r = await this.#send(windowId, rec, {
7573
+ instructionId: `login-${callId}`.replace(/[^0-9a-zA-Z_-]/g, "").slice(0, 64) || "login",
7574
+ sessionId: "",
7575
+ expireAt: 0,
7576
+ answer: { kind: "probe", toolName, login: true, ...fingerprint2 ? { fingerprint: fingerprint2 } : {} }
7577
+ });
7578
+ return r?.status === "started";
7579
+ } catch {
7580
+ return false;
7581
+ }
7582
+ }
7325
7583
  async #answer(card, reply) {
7326
7584
  let spec;
7327
7585
  if (card.toolName === "AskUserQuestion") {
@@ -7374,6 +7632,10 @@ var WorkbuddyBridge = class {
7374
7632
  }
7375
7633
  return n;
7376
7634
  }
7635
+ /** 这个会话手上有没有在等的卡 */
7636
+ hasCard(sessionId) {
7637
+ return this.#cards.has(sessionId);
7638
+ }
7377
7639
  /** 这个窗口里在等的卡所属的会话号(没登录回报时带上) */
7378
7640
  sessionOfWindow(windowId) {
7379
7641
  for (const card of this.#cards.values()) if (card.windowId === windowId) return card.sessionId;
@@ -7783,6 +8045,12 @@ var TrustWatcher = class {
7783
8045
  #notify() {
7784
8046
  this.#d.notify({ kind: this.#kind ?? "trust", ...this.#count ? { count: this.#count } : {}, ...this.#repoRoot ? { repoRoot: this.#repoRoot } : {} });
7785
8047
  }
8048
+ #mismatchLogged = false;
8049
+ #mismatch(msg) {
8050
+ if (this.#mismatchLogged) return;
8051
+ this.#mismatchLogged = true;
8052
+ this.#d.log(msg);
8053
+ }
7786
8054
  tick(lines2) {
7787
8055
  const v = lines2 ? inspectTrust(lines2, this.#d.tool) : null;
7788
8056
  if (!v || v.kind !== this.#kind) {
@@ -7791,6 +8059,7 @@ var TrustWatcher = class {
7791
8059
  this.#contactAt = null;
7792
8060
  this.#kind = v ? v.kind : null;
7793
8061
  this.#repoRoot = void 0;
8062
+ this.#mismatchLogged = false;
7794
8063
  if (!v) return;
7795
8064
  }
7796
8065
  this.#count = v.count;
@@ -7815,8 +8084,10 @@ var TrustWatcher = class {
7815
8084
  }
7816
8085
  const scope = this.#scope();
7817
8086
  if (scope?.kind === "repo") {
8087
+ const shownRoot = v.widenedRoot ?? "";
7818
8088
  if (!repoAnswerable(v, this.#d.tool, this.#forms, scope.root)) {
7819
- this.#d.log("\u4FE1\u4EFB\u6846\u4F1A\u843D\u5230 git \u4ED3\u5E93\u6839\uFF0C\u4F46\u6846\u91CC\u7684\u76EE\u5F55\u548C\u6211\u4EEC\u627E\u5230\u7684\u4ED3\u5E93\u6839\u5BF9\u4E0D\u4E0A\uFF0C\u4E0D\u63A8\u8BBE\u5907\uFF0C\u8BF7\u5728\u7535\u8111\u4E0A\u786E\u8BA4");
8089
+ this.#reported = false;
8090
+ this.#mismatch(`\u4FE1\u4EFB\u6846\u4F1A\u843D\u5230 git \u4ED3\u5E93\u6839\uFF0C\u4F46\u6846\u91CC\u7684\u76EE\u5F55\u548C\u6211\u4EEC\u627E\u5230\u7684\u4ED3\u5E93\u6839\u5BF9\u4E0D\u4E0A\uFF0C\u6682\u4E0D\u63A8\u8BBE\u5907\uFF08\u6846\u91CC\u4ED3\u5E93\u6839\u300C${shownRoot}\u300D\uFF0C\u6211\u4EEC\u627E\u5230\u300C${scope.root}\u300D\uFF09`);
7820
8091
  return;
7821
8092
  }
7822
8093
  this.#d.log("\u4FE1\u4EFB\u6846\u4F1A\u843D\u5230 git \u4ED3\u5E93\u6839\uFF0C\u63A8\u8BBE\u5907\u786E\u8BA4\uFF08\u4ED3\u5E93\u6839\u5361\u7247\uFF09");
@@ -7836,7 +8107,8 @@ var TrustWatcher = class {
7836
8107
  return;
7837
8108
  }
7838
8109
  if (!pathShown(v, this.#forms)) {
7839
- this.#d.log("\u4FE1\u4EFB\u6846\u91CC\u7684\u76EE\u5F55\u548C\u7A97\u53E3\u5DE5\u4F5C\u76EE\u5F55\u5BF9\u4E0D\u4E0A\uFF0C\u4E0D\u63A8\u8BBE\u5907");
8110
+ this.#reported = false;
8111
+ this.#mismatch("\u4FE1\u4EFB\u6846\u91CC\u7684\u76EE\u5F55\u548C\u7A97\u53E3\u5DE5\u4F5C\u76EE\u5F55\u5BF9\u4E0D\u4E0A\uFF0C\u6682\u4E0D\u63A8\u8BBE\u5907");
7840
8112
  return;
7841
8113
  }
7842
8114
  this.#d.log("\u4FE1\u4EFB\u6846\u51FA\u73B0\uFF0C\u63A8\u8BBE\u5907\u786E\u8BA4");
@@ -8114,6 +8386,11 @@ function defaultSend2(windowId, rec, req2) {
8114
8386
  // src/daemon/loginRequired.ts
8115
8387
  var LOGIN_REASON = "agent_not_logged_in";
8116
8388
  var LOGIN_CANCEL_REASON = "\u9700\u8981\u767B\u5F55\uFF1AWorkBuddy \u547D\u4EE4\u884C\u6CA1\u767B\u5F55\uFF0C\u8BF7\u5728\u7535\u8111\u7EC8\u7AEF\u91CC\u767B\u5F55\u4E00\u6B21";
8389
+ function loginBlockedState(st, notAfter = Infinity) {
8390
+ if (!st || st.tool !== "workbuddy" || st.state !== "ask_approval") return null;
8391
+ if (st.updatedAt > notAfter) return null;
8392
+ return { ...st, state: "failed", updatedAt: st.updatedAt + 1 };
8393
+ }
8117
8394
  function windowReplyAction(launch, status) {
8118
8395
  if (status === "ack") return "skip";
8119
8396
  if (launch.ended) return "ended";
@@ -8123,6 +8400,28 @@ function windowReplyAction(launch, status) {
8123
8400
  }
8124
8401
  return status === "not_here" ? "fallback" : "report";
8125
8402
  }
8403
+ var LOGIN_BLOCKED_RETRY_MS = [2e3, 6e3];
8404
+ function markLoginBlocked(sid, d) {
8405
+ let baseline;
8406
+ try {
8407
+ baseline = Math.max(d.now(), d.readState(sid)?.updatedAt ?? 0);
8408
+ } catch {
8409
+ baseline = d.now();
8410
+ }
8411
+ const tryOnce = () => {
8412
+ if (d.stopped() || d.hasCard(sid)) return;
8413
+ try {
8414
+ const next = loginBlockedState(d.readState(sid), baseline);
8415
+ if (!next) return;
8416
+ d.writeState(next);
8417
+ d.log(`\u4F1A\u8BDD ${sid.slice(0, 8)} \u6388\u6743\u88AB\u767B\u5F55\u9875\u6321\u4F4F\uFF0C\u72B6\u6001 ask_approval \u6539\u6210 failed\uFF08\u670D\u52A1\u7AEF\u6CA1\u6709\u300C\u9700\u8981\u767B\u5F55\u300D\u72B6\u6001\u4E5F\u6CA1\u6709 reason \u5B57\u6BB5\uFF0Cfailed \u8BA9\u8BBE\u5907\u63D0\u793A\u53BB\u7535\u8111\u4E0A\u770B\uFF09`);
8418
+ d.report();
8419
+ } catch {
8420
+ }
8421
+ };
8422
+ tryOnce();
8423
+ for (const ms of LOGIN_BLOCKED_RETRY_MS) d.later(tryOnce, ms);
8424
+ }
8126
8425
  function isLoginNotify(req2) {
8127
8426
  return !!req2 && !!req2.hook && req2.hook.hook_event_name === LOGIN_EVENT;
8128
8427
  }
@@ -8132,6 +8431,10 @@ var LoginRequired = class {
8132
8431
  constructor(d) {
8133
8432
  this.#d = d;
8134
8433
  }
8434
+ /** 这个窗口认出过登录页(WorkbuddyBridge 据此对新授权卡先问包装层登录页还在不在) */
8435
+ isLoginWindow(windowId) {
8436
+ return this.#done.has(windowId);
8437
+ }
8135
8438
  onNotify(req2) {
8136
8439
  try {
8137
8440
  this.#onNotify(req2);
@@ -8155,6 +8458,7 @@ var LoginRequired = class {
8155
8458
  const short = windowId.slice(0, 8);
8156
8459
  const sid = this.#d.sessionOfWindow(windowId);
8157
8460
  const n = this.#d.cancelCards(windowId, LOGIN_CANCEL_REASON);
8461
+ if (n > 0 && sid) this.#d.markBlocked?.(sid);
8158
8462
  this.#d.log(`WorkBuddy \u7A97\u53E3 ${short} \u505C\u5728\u767B\u5F55\u9875\uFF1A\u547D\u4EE4\u884C\u6CA1\u767B\u5F55\uFF08\u684C\u9762 App \u7684\u767B\u5F55\u4E0D\u5171\u4EAB\u7ED9\u547D\u4EE4\u884C\uFF09\uFF0C\u9700\u8981\u5728\u7EC8\u7AEF\u91CC\u767B\u5F55\u4E00\u6B21\uFF1B\u64A4\u5361 ${n} \u5F20`);
8159
8463
  const entry = typeof rec.launchId === "string" ? this.#d.launchOf(rec.launchId) : void 0;
8160
8464
  if (!entry) {
@@ -8185,7 +8489,7 @@ function pidAlive4(pid) {
8185
8489
  }
8186
8490
 
8187
8491
  // src/daemon/dumateWatch.ts
8188
- import { execFileSync as execFileSync11, spawn as spawn3 } from "child_process";
8492
+ import { execFileSync as execFileSync12, spawn as spawn3 } from "child_process";
8189
8493
  import { readdirSync as readdirSync8, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
8190
8494
  import { request as httpRequest } from "http";
8191
8495
  import { join as join29 } from "path";
@@ -8323,11 +8627,11 @@ var keyCache = null;
8323
8627
  function defaultDiscover() {
8324
8628
  if (process.platform === "win32" || typeof process.getuid !== "function") return null;
8325
8629
  try {
8326
- const ps = execFileSync11("ps", ["-A", "-ww", "-o", "pid=,uid=,args="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8630
+ const ps = execFileSync12("ps", ["-A", "-ww", "-o", "pid=,uid=,args="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8327
8631
  const hit = parseDumateServe(ps, process.getuid());
8328
8632
  if (!hit) return null;
8329
8633
  if (keyCache && keyCache.pid === hit.pid && keyCache.port === hit.port) return keyCache;
8330
- const env = execFileSync11("ps", ["eww", "-p", String(hit.pid), "-o", "command="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8634
+ const env = execFileSync12("ps", ["eww", "-p", String(hit.pid), "-o", "command="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
8331
8635
  const key = parseInappKey(env);
8332
8636
  keyCache = key ? { ...hit, key } : null;
8333
8637
  return keyCache;
@@ -8969,7 +9273,7 @@ function runHook(node, input, agentPid) {
8969
9273
  }
8970
9274
 
8971
9275
  // src/daemon/codexWatch.ts
8972
- import { execFileSync as execFileSync12, spawn as spawn4 } from "child_process";
9276
+ import { execFileSync as execFileSync13, spawn as spawn4 } from "child_process";
8973
9277
  import WebSocket from "ws";
8974
9278
  var TURN_LIMIT_MS = 10 * 60 * 1e3;
8975
9279
  var QUEUE_MAX_MS = 10 * 60 * 1e3;
@@ -9014,7 +9318,7 @@ function discoverAppServers(configured) {
9014
9318
  return parseAppServerUrls((winProcs() ?? []).map((p) => p.cmdline).join("\n"));
9015
9319
  }
9016
9320
  try {
9017
- const out = execFileSync12("ps", ["-A", "-ww", "-o", "args="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
9321
+ const out = execFileSync13("ps", ["-A", "-ww", "-o", "args="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
9018
9322
  return parseAppServerUrls(out);
9019
9323
  } catch {
9020
9324
  return [];
@@ -10079,8 +10383,15 @@ function launchPending(runId) {
10079
10383
  }
10080
10384
 
10081
10385
  // src/core/interrupt.ts
10082
- import { closeSync as closeSync6, openSync as openSync6, readdirSync as readdirSync9, readSync as readSync5, statSync as statSync13 } from "fs";
10083
- import { join as join32 } from "path";
10386
+ import { closeSync as closeSync7, openSync as openSync7, readdirSync as readdirSync9, readSync as readSync6, statSync as statSync13 } from "fs";
10387
+ import { dirname as dirname18, join as join32 } from "path";
10388
+ var INTERRUPT_TOOLS = ["claude_code", "qoder_cn", "qoder"];
10389
+ function transcriptHome(tool) {
10390
+ if (tool === "claude_code") return claudeHome();
10391
+ if (tool === "qoder_cn") return dirname18(qoderCnSettingsPath());
10392
+ if (tool === "qoder") return dirname18(qoderSettingsPath());
10393
+ return null;
10394
+ }
10084
10395
  var TAIL = 64 * 1024;
10085
10396
  var SID_RE2 = /^[A-Za-z0-9_-]{1,64}$/;
10086
10397
  function userText2(content) {
@@ -10094,8 +10405,8 @@ function interruptedAt(file) {
10094
10405
  const st = statSync13(file);
10095
10406
  const len = Math.min(st.size, TAIL);
10096
10407
  const buf = Buffer.alloc(len);
10097
- fd = openSync6(file, "r");
10098
- readSync5(fd, buf, 0, len, st.size - len);
10408
+ fd = openSync7(file, "r");
10409
+ readSync6(fd, buf, 0, len, st.size - len);
10099
10410
  const lines2 = buf.toString("utf8").split("\n");
10100
10411
  for (let i = lines2.length - 1; i >= 0; i--) {
10101
10412
  const line = lines2[i];
@@ -10124,7 +10435,7 @@ function interruptedAt(file) {
10124
10435
  } finally {
10125
10436
  if (fd !== void 0) {
10126
10437
  try {
10127
- closeSync6(fd);
10438
+ closeSync7(fd);
10128
10439
  } catch {
10129
10440
  }
10130
10441
  }
@@ -10159,7 +10470,7 @@ function claudeTranscriptFile(sessionId, cwd, home = claudeHome()) {
10159
10470
  var INTERRUPT_SETTLE_MS = 2e3;
10160
10471
  var INTERRUPT_TOLERANCE_MS = 5e3;
10161
10472
  function shouldSettleInterrupt(st, markerAt, now) {
10162
- if (markerAt === null || st.tool !== "claude_code") return false;
10473
+ if (markerAt === null || !INTERRUPT_TOOLS.includes(st.tool)) return false;
10163
10474
  if (st.state !== "watch" && st.state !== "ask_approval") return false;
10164
10475
  if (st.interactive !== true && st.embedded !== true) return false;
10165
10476
  if (now - markerAt < INTERRUPT_SETTLE_MS) return false;
@@ -10169,7 +10480,7 @@ function shouldSettleInterrupt(st, markerAt, now) {
10169
10480
  }
10170
10481
 
10171
10482
  // src/daemon/openWindow.ts
10172
- import { execFile as execFile4, execFileSync as execFileSync13, spawn as spawn5 } from "child_process";
10483
+ import { execFile as execFile5, execFileSync as execFileSync14, spawn as spawn5 } from "child_process";
10173
10484
  import { chmodSync as chmodSync9, mkdirSync as mkdirSync23, rmSync as rmSync14, writeFileSync as writeFileSync21 } from "fs";
10174
10485
  import { userInfo } from "os";
10175
10486
  import { join as join33 } from "path";
@@ -10220,14 +10531,14 @@ var defaultGuiDeps = () => ({
10220
10531
  env: process.env,
10221
10532
  macManager: () => {
10222
10533
  try {
10223
- return execFileSync13("launchctl", ["managername"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
10534
+ return execFileSync14("launchctl", ["managername"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
10224
10535
  } catch {
10225
10536
  return "";
10226
10537
  }
10227
10538
  },
10228
10539
  macConsoleUser: () => {
10229
10540
  try {
10230
- return execFileSync13("stat", ["-f", "%Su", "/dev/console"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
10541
+ return execFileSync14("stat", ["-f", "%Su", "/dev/console"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 });
10231
10542
  } catch {
10232
10543
  return "";
10233
10544
  }
@@ -10241,7 +10552,7 @@ var defaultGuiDeps = () => ({
10241
10552
  },
10242
10553
  winSessionId: () => {
10243
10554
  try {
10244
- const out = execFileSync13("tasklist", ["/FI", `PID eq ${process.pid}`, "/FO", "CSV", "/NH"], {
10555
+ const out = execFileSync14("tasklist", ["/FI", `PID eq ${process.pid}`, "/FO", "CSV", "/NH"], {
10245
10556
  encoding: "utf8",
10246
10557
  stdio: ["ignore", "pipe", "ignore"],
10247
10558
  windowsHide: true,
@@ -10259,7 +10570,7 @@ var defaultOpenerDeps = (findWt2) => ({
10259
10570
  platform: process.platform,
10260
10571
  findWt: findWt2,
10261
10572
  run: (file, args) => new Promise((resolve10, reject) => {
10262
- execFile4(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) : resolve10());
10573
+ execFile5(file, args, { windowsHide: true, timeout: 15e3 }, (err) => err ? reject(err) : resolve10());
10263
10574
  }),
10264
10575
  spawnDetached: (file, args) => {
10265
10576
  const c = spawn5(file, args, { detached: true, stdio: "ignore", shell: false, windowsHide: false });
@@ -10964,7 +11275,7 @@ function defaultCodexAppServers() {
10964
11275
  }
10965
11276
  function opencodeReady() {
10966
11277
  const mod = ocPluginModule(installedHookPath());
10967
- return isOcPluginInstalled(opencodePluginPath()) && existsSync20(mod) && existsSync20(join36(dirname18(mod), "ocTranslate.mjs"));
11278
+ return isOcPluginInstalled(opencodePluginPath()) && existsSync20(mod) && existsSync20(join36(dirname19(mod), "ocTranslate.mjs"));
10968
11279
  }
10969
11280
  var INTERNAL_PROMPT_MARK = "[DeskBuddy]";
10970
11281
  function orphanResumeText(summary2) {
@@ -11135,7 +11446,9 @@ var Daemon = class {
11135
11446
  this.#workbuddy = new WorkbuddyBridge({
11136
11447
  gate: (req2, signal) => this.#coord.onGate(req2, signal),
11137
11448
  log,
11138
- enabled: () => loadConfig().enabledTools.includes("workbuddy")
11449
+ enabled: () => loadConfig().enabledTools.includes("workbuddy"),
11450
+ loginWindow: (windowId) => this.#login.isLoginWindow(windowId),
11451
+ loginBlocked: (_windowId, sid) => this.#markLoginBlocked(sid)
11139
11452
  });
11140
11453
  this.#trust = new TrustBridge({
11141
11454
  gate: (req2, signal) => this.#coord.onGate(req2, signal),
@@ -11147,7 +11460,8 @@ var Daemon = class {
11147
11460
  launchOf: (id) => this.#launches.get(id),
11148
11461
  report: (cmd, sid, status, reason) => this.#report(cmd, sid, status, "window", reason),
11149
11462
  cancelCards: (windowId, reason) => this.#workbuddy.cancelWindow(windowId, reason) + this.#trust.cancelWindow(windowId, reason),
11150
- sessionOfWindow: (windowId) => this.#workbuddy.sessionOfWindow(windowId)
11463
+ sessionOfWindow: (windowId) => this.#workbuddy.sessionOfWindow(windowId),
11464
+ markBlocked: (sid) => this.#markLoginBlocked(sid)
11151
11465
  });
11152
11466
  this.#qwenwork = new QwenWorkBridge({
11153
11467
  gate: (req2, signal) => this.#coord.onGate(req2, signal),
@@ -11278,6 +11592,22 @@ var Daemon = class {
11278
11592
  }, CODEX_IDLE_SETTLE_MS);
11279
11593
  t.unref?.();
11280
11594
  }
11595
+ /**
11596
+ * WorkBuddy 窗口停在登录页、授权卡没推 / 被撤(loginRequired.ts loginBlockedState):会话状态从 ask_approval 改成 failed 并上报。
11597
+ * 推卡的通知与写 ask_approval 的 hook 是同一事件的两次独立调用,谁先到不一定:现在改一次,隔几秒再各看一次
11598
+ */
11599
+ #markLoginBlocked(sid) {
11600
+ markLoginBlocked(sid, {
11601
+ now: this.#deps.now,
11602
+ readState,
11603
+ writeState,
11604
+ hasCard: (id) => this.#workbuddy.hasCard(id),
11605
+ stopped: () => this.#stopped,
11606
+ log,
11607
+ report: () => void this.#reportAlive().catch(() => void 0),
11608
+ later: (fn, ms) => void setTimeout(fn, ms).unref?.()
11609
+ });
11610
+ }
11281
11611
  /** 状态还停在忙且 since 之后没有新事件 → 改成 done。改了返回 true */
11282
11612
  #settleBusy(sessionId, tool, since, why) {
11283
11613
  try {
@@ -11291,34 +11621,39 @@ var Daemon = class {
11291
11621
  }
11292
11622
  }
11293
11623
  /**
11294
- * 扫一遍 Claude Code 窗口里还在忙的会话:transcript 显示已被打断就补落 done(Bug 表 160);
11624
+ * 扫一遍 Claude Code / Qoder 窗口里还在忙的会话:transcript 显示已被打断就补落 done(Bug 表 160 / 204);
11295
11625
  * 会话开在 DeskBuddy 窗口里的,同时把窗口的 .last 补成 Stop(包装层据此判空闲,否则设备回复一直排队到过期)
11296
11626
  */
11297
11627
  #settleInterrupted() {
11298
- if (this.#stopped || !loadConfig().enabledTools.includes("claude_code")) return;
11628
+ if (this.#stopped) return;
11629
+ const enabled = loadConfig().enabledTools;
11630
+ const tools = INTERRUPT_TOOLS.filter((t) => enabled.includes(t));
11631
+ if (tools.length === 0) return;
11299
11632
  const now = this.#deps.now();
11300
- let changed = false;
11301
11633
  const alive = /* @__PURE__ */ new Set();
11302
- for (const st of this.#aliveStates("claude_code")) {
11303
- alive.add(st.sessionId);
11304
- if (!BUSY_STATES2.has(st.state) || st.interactive !== true && st.embedded !== true) {
11305
- this.#transcriptSeen.delete(st.sessionId);
11306
- continue;
11307
- }
11308
- const file = this.#transcriptOf(st, now);
11309
- if (!file) continue;
11310
- const sig = transcriptSig(file);
11311
- if (sig && this.#transcriptSeen.get(st.sessionId) === sig) continue;
11312
- const at = interruptedAt(file);
11313
- if (sig && (at === null || now - at >= INTERRUPT_SETTLE_MS)) this.#transcriptSeen.set(st.sessionId, sig);
11314
- if (at === null || !shouldSettleInterrupt(st, at, now)) continue;
11315
- if (this.#settleBusy(st.sessionId, "claude_code", st.updatedAt, "\u5728\u7A97\u53E3\u91CC\u88AB\u6253\u65AD\uFF08Claude Code \u4E0D\u53D1 Stop\uFF09")) {
11316
- changed = true;
11317
- this.#idleWindowAfterInterrupt(st.sessionId, at);
11634
+ for (const tool of tools) {
11635
+ let changed = false;
11636
+ for (const st of this.#aliveStates(tool)) {
11637
+ alive.add(st.sessionId);
11638
+ if (!BUSY_STATES2.has(st.state) || st.interactive !== true && st.embedded !== true) {
11639
+ this.#transcriptSeen.delete(st.sessionId);
11640
+ continue;
11641
+ }
11642
+ const file = this.#transcriptOf(st, now);
11643
+ if (!file) continue;
11644
+ const sig = transcriptSig(file);
11645
+ if (sig && this.#transcriptSeen.get(st.sessionId) === sig) continue;
11646
+ const at = interruptedAt(file);
11647
+ if (sig && (at === null || now - at >= INTERRUPT_SETTLE_MS)) this.#transcriptSeen.set(st.sessionId, sig);
11648
+ if (at === null || !shouldSettleInterrupt(st, at, now)) continue;
11649
+ if (this.#settleBusy(st.sessionId, tool, st.updatedAt, `\u5728\u7A97\u53E3\u91CC\u88AB\u6253\u65AD\uFF08${tool} \u4E0D\u53D1 Stop\uFF09`)) {
11650
+ changed = true;
11651
+ this.#idleWindowAfterInterrupt(st.sessionId, at);
11652
+ }
11318
11653
  }
11654
+ if (changed) void this.#reportTool(tool);
11319
11655
  }
11320
11656
  for (const m of [this.#transcripts, this.#transcriptMiss, this.#transcriptSeen]) for (const k of [...m.keys()]) if (!alive.has(k)) m.delete(k);
11321
- if (changed) void this.#reportTool("claude_code");
11322
11657
  }
11323
11658
  /** 会话的 transcript:hook 记在状态里的优先;没有再按目录找,找不到的一段时间内不再找 */
11324
11659
  #transcriptOf(st, now) {
@@ -11327,7 +11662,8 @@ var Daemon = class {
11327
11662
  if (cached && existsSync20(cached)) return cached;
11328
11663
  const missAt = this.#transcriptMiss.get(st.sessionId);
11329
11664
  if (missAt !== void 0 && now - missAt < TRANSCRIPT_MISS_TTL_MS) return null;
11330
- const f = claudeTranscriptFile(st.sessionId, st.cwd);
11665
+ const home = transcriptHome(st.tool);
11666
+ const f = home ? claudeTranscriptFile(st.sessionId, st.cwd, home) : null;
11331
11667
  if (f) {
11332
11668
  this.#transcripts.set(st.sessionId, f);
11333
11669
  this.#transcriptMiss.delete(st.sessionId);
@@ -12396,6 +12732,7 @@ async function runDaemonCommand(opts = {}) {
12396
12732
  `);
12397
12733
  return 1;
12398
12734
  }
12735
+ void warmClaudeProbe();
12399
12736
  const d = daemon;
12400
12737
  const updater = opts.foreground ? null : new AutoUpdater({
12401
12738
  currentVersion: packageVersion(),
@@ -13340,7 +13677,7 @@ var Injector = class {
13340
13677
  import { spawn as spawn6 } from "child_process";
13341
13678
  import { realpathSync as realpathSync7 } from "fs";
13342
13679
  import { constants as constants2 } from "os";
13343
- import { dirname as dirname19, resolve as resolve9 } from "path";
13680
+ import { dirname as dirname20, resolve as resolve9 } from "path";
13344
13681
  var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
13345
13682
  "agents",
13346
13683
  "attach",
@@ -13404,7 +13741,7 @@ function real(p) {
13404
13741
  function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
13405
13742
  const win = (d.platform ?? process.platform) === "win32";
13406
13743
  const skipKeys = /* @__PURE__ */ new Set([norm2(skip, win), norm2(real(skip), win)]);
13407
- const inSkip = (file) => skipKeys.has(norm2(dirname19(file), win)) || skipKeys.has(norm2(dirname19(real(file)), win));
13744
+ const inSkip = (file) => skipKeys.has(norm2(dirname20(file), win)) || skipKeys.has(norm2(dirname20(real(file)), win));
13408
13745
  const dd = { ...d, skipDirs: [...d.skipDirs ?? [], skip] };
13409
13746
  const name = SHIM_COMMANDS[tool];
13410
13747
  if (!name) {
@@ -13412,7 +13749,11 @@ function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
13412
13749
  return loc && !inSkip(loc.prefixArgs[0] && /\.(?:c|m)?js$/i.test(loc.prefixArgs[0]) ? loc.prefixArgs[0] : loc.file) ? loc : null;
13413
13750
  }
13414
13751
  if (win && tool === "claude_code") {
13415
- for (const p of winClaudeExeCandidates(dd)) if (dd.exists(p) && !inSkip(p)) return { file: p, prefixArgs: [] };
13752
+ return pickWinClaude(dd, (src, loc) => {
13753
+ if (inSkip(src)) return false;
13754
+ const target = loc.prefixArgs[0] && /\.(?:c|m)?js$/i.test(loc.prefixArgs[0]) ? loc.prefixArgs[0] : loc.file;
13755
+ return !inSkip(target);
13756
+ });
13416
13757
  }
13417
13758
  for (const f of whichAllIn(name, dd)) {
13418
13759
  if (inSkip(f)) continue;
@@ -13427,7 +13768,7 @@ function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
13427
13768
  function withoutShimFlag(env) {
13428
13769
  const out = { ...env };
13429
13770
  delete out.DESKBUDDY_SHIM;
13430
- return out;
13771
+ return scrubAgentEnv(out);
13431
13772
  }
13432
13773
  function runDirect(real2, args, env = process.env) {
13433
13774
  return new Promise((done) => {
@@ -13484,34 +13825,10 @@ function runDirect(real2, args, env = process.env) {
13484
13825
 
13485
13826
  // src/wrap/codexLaunch.ts
13486
13827
  import { execFileSync as execFileSync15, spawn as spawn7 } from "child_process";
13487
- import { closeSync as closeSync7, mkdirSync as mkdirSync27, openSync as openSync7, readdirSync as readdirSync11, readFileSync as readFileSync26, rmSync as rmSync21, statSync as statSync16, truncateSync as truncateSync2, writeFileSync as writeFileSync26, renameSync as renameSync19 } from "fs";
13828
+ import { closeSync as closeSync8, mkdirSync as mkdirSync27, openSync as openSync8, readdirSync as readdirSync11, readFileSync as readFileSync26, rmSync as rmSync21, statSync as statSync16, truncateSync as truncateSync2, writeFileSync as writeFileSync26, renameSync as renameSync19 } from "fs";
13488
13829
  import { request as request2 } from "http";
13489
13830
  import { createServer as createServer4 } from "net";
13490
13831
  import { join as join38 } from "path";
13491
-
13492
- // src/core/codexServer.ts
13493
- import { execFileSync as execFileSync14 } from "child_process";
13494
- function isAppServerArgs(args, url) {
13495
- return /\bapp-server\b/.test(args) && new RegExp(`--listen[= ]${url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s|$)`).test(args);
13496
- }
13497
- function processArgs(pid, platform = process.platform, procs, fresh = false) {
13498
- if (platform === "win32") {
13499
- const list = procs ? procs() : winProcs(Date.now(), { force: fresh });
13500
- const p = (list ?? []).find((x) => x.pid === pid);
13501
- return p?.args || null;
13502
- }
13503
- try {
13504
- const out = execFileSync14("ps", ["-ww", "-o", "args=", "-p", String(pid)], {
13505
- encoding: "utf8",
13506
- stdio: ["ignore", "pipe", "ignore"]
13507
- });
13508
- return out.trim() || null;
13509
- } catch {
13510
- return null;
13511
- }
13512
- }
13513
-
13514
- // src/wrap/codexLaunch.ts
13515
13832
  var TAKEOVER_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "--config", "-m", "--model", "-s", "--sandbox", "-a", "--ask-for-approval", "-C", "--cd", "--enable", "--disable"]);
13516
13833
  var SHORT_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "-m", "-s", "-a", "-C"]);
13517
13834
  var TAKEOVER_FLAGS = /* @__PURE__ */ new Set(["--search", "--no-alt-screen", "--dangerously-bypass-approvals-and-sandbox"]);
@@ -13738,7 +14055,7 @@ function startServer(real2, url, env, cwd, platform = process.platform) {
13738
14055
  if (statSync16(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
13739
14056
  } catch {
13740
14057
  }
13741
- const fd = openSync7(logPath2, "a");
14058
+ const fd = openSync8(logPath2, "a");
13742
14059
  try {
13743
14060
  const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
13744
14061
  delete childEnv2.DESKBUDDY_SHIM;
@@ -13748,7 +14065,7 @@ function startServer(real2, url, env, cwd, platform = process.platform) {
13748
14065
  child.unref();
13749
14066
  return child.pid ?? null;
13750
14067
  } finally {
13751
- closeSync7(fd);
14068
+ closeSync8(fd);
13752
14069
  }
13753
14070
  }
13754
14071
  function killServer(pid, platform = process.platform, run2 = taskkill) {
@@ -14004,8 +14321,10 @@ function isLoginPage(lines2) {
14004
14321
  if (title < 0) return false;
14005
14322
  const below = lines2.slice(title + 1);
14006
14323
  const options = below.filter((l) => OPTION.test(l)).length;
14007
- return options >= 2 && below.some((l) => HINT.test(l));
14324
+ const tail2 = lines2.filter((l) => l.trim() !== "").slice(-HINT_TAIL_LINES);
14325
+ return options >= 2 && tail2.some((l) => HINT.test(l));
14008
14326
  }
14327
+ var HINT_TAIL_LINES = 3;
14009
14328
  var LOGIN_STABLE_MS = 500;
14010
14329
  var LoginWatcher = class {
14011
14330
  constructor(d) {
@@ -14046,6 +14365,11 @@ async function applyAnswer(req2, io, trust) {
14046
14365
  if (req2.expireAt > 0 && Date.now() > req2.expireAt) return { instructionId: id, status: "rejected", reason: "expired" };
14047
14366
  const a = req2.answer;
14048
14367
  if ((a.kind === "trust" || a.kind === "probe" && a.trust) && !trust) return { instructionId: id, status: "rejected", reason: "unsupported" };
14368
+ if (a.kind === "probe" && a.login) {
14369
+ if (!io.loginOnScreen) return { instructionId: id, status: "rejected", reason: "unsupported" };
14370
+ const onLogin = io.loginOnScreen() && !(a.fingerprint && isPermissionDialog(io.screen() ?? [], a.toolName, a.fingerprint));
14371
+ return onLogin ? { instructionId: id, status: "started", mode: "window" } : { instructionId: id, status: "rejected", reason: "not_waiting" };
14372
+ }
14049
14373
  if (!io.screen()) return { instructionId: id, status: "rejected", reason: "no_screen" };
14050
14374
  if (a.kind === "probe") {
14051
14375
  const waiting = a.trust ? trustWaiting(io.screen() ?? [], trust?.tool ?? "", a.prompt ?? "trust") : stillWaiting(io.screen() ?? [], a);
@@ -14210,7 +14534,8 @@ async function runAnswerWindow(tool, real2, args, env, windowId, d) {
14210
14534
  }
14211
14535
  },
14212
14536
  write: toPty,
14213
- sleep: (ms) => new Promise((res) => setTimeout(res, ms))
14537
+ sleep: (ms) => new Promise((res) => setTimeout(res, ms)),
14538
+ ...login ? { loginOnScreen: () => login.onScreen } : {}
14214
14539
  }, trustCtx);
14215
14540
  if (req2.answer.kind !== "probe") d.log(`[${windowId.slice(0, 8)}] \u8BBE\u5907\u7B54\u590D\uFF08${req2.answer.kind}\uFF09\uFF1A${r.status}${r.reason ? " " + r.reason : ""}`);
14216
14541
  reply(r);
@@ -14273,7 +14598,7 @@ function childEnv(src, windowId) {
14273
14598
  }
14274
14599
  env.DESKBUDDY_WINDOW = windowId;
14275
14600
  env.DESKBUDDY_WRAPPED = "1";
14276
- return env;
14601
+ return scrubAgentEnv(env);
14277
14602
  }
14278
14603
  function wrapLog(line) {
14279
14604
  try {
@@ -14771,6 +15096,7 @@ async function main(argv) {
14771
15096
  } catch {
14772
15097
  }
14773
15098
  }
15099
+ scrubForCommand(cmd);
14774
15100
  switch (cmd) {
14775
15101
  case void 0:
14776
15102
  case "-h":