deskbuddy 0.2.0-beta.19 → 0.2.0-beta.20

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands/daemon.ts
4
- import { writeFileSync as writeFileSync25, rmSync as rmSync18 } from "fs";
4
+ import { writeFileSync as writeFileSync27, rmSync as rmSync20 } from "fs";
5
5
 
6
6
  // src/core/api.ts
7
7
  import { execFileSync } from "child_process";
@@ -13,7 +13,7 @@ import { release } from "os";
13
13
  import { randomUUID } from "crypto";
14
14
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
15
15
  import { homedir, hostname } from "os";
16
- import { join } from "path";
16
+ import { dirname, join } from "path";
17
17
  function defaultRemoteCwd(home = homedir(), platform = process.platform, exists = existsSync) {
18
18
  const desktop = join(home, "Desktop");
19
19
  if (platform === "win32" && !exists(desktop)) {
@@ -58,6 +58,9 @@ function claudeSettingsPath() {
58
58
  function workbuddySettingsPath() {
59
59
  return join(process.env.CODEBUDDY_CONFIG_DIR || join(homedir(), ".workbuddy"), "settings.json");
60
60
  }
61
+ function workbuddyDbPath() {
62
+ return join(dirname(workbuddySettingsPath()), "workbuddy.db");
63
+ }
61
64
  function qoderCnSettingsPath() {
62
65
  return join(process.env.DESKBUDDY_QODER_CN_DIR || join(homedir(), ".qoder-cn"), "settings.json");
63
66
  }
@@ -89,6 +92,14 @@ function codexHome() {
89
92
  function installedHookPath() {
90
93
  return join(deskbuddyHome(), "hook.cjs");
91
94
  }
95
+ function parseWorkbuddySetup(v) {
96
+ if (!v || typeof v !== "object" || Array.isArray(v)) return void 0;
97
+ const o = v;
98
+ const out = {};
99
+ if (o.declined === true) out.declined = true;
100
+ if (typeof o.askedAt === "number" && Number.isFinite(o.askedAt)) out.askedAt = o.askedAt;
101
+ return Object.keys(out).length > 0 ? out : void 0;
102
+ }
92
103
  function loadConfig() {
93
104
  const fallback = {
94
105
  clientId: randomUUID(),
@@ -115,7 +126,8 @@ function loadConfig() {
115
126
  approvalPolicy: parsed.approvalPolicy === "all" ? "all" : parsed.approvalPolicy === "commands" ? "commands" : void 0,
116
127
  codexAppServer: typeof parsed.codexAppServer === "string" ? parsed.codexAppServer : void 0,
117
128
  autoUpdate: typeof parsed.autoUpdate === "boolean" ? parsed.autoUpdate : void 0,
118
- disabledTools: Array.isArray(parsed.disabledTools) ? parsed.disabledTools.filter((t) => typeof t === "string") : void 0
129
+ disabledTools: Array.isArray(parsed.disabledTools) ? parsed.disabledTools.filter((t) => typeof t === "string") : void 0,
130
+ workbuddySetup: parseWorkbuddySetup(parsed.workbuddySetup)
119
131
  };
120
132
  } catch {
121
133
  return fallback;
@@ -139,7 +151,7 @@ function ensureClientId() {
139
151
  // src/core/hookFile.ts
140
152
  import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "fs";
141
153
  import { createRequire } from "module";
142
- import { dirname, join as join2 } from "path";
154
+ import { dirname as dirname2, join as join2 } from "path";
143
155
  import { fileURLToPath } from "url";
144
156
 
145
157
  // src/core/semver.ts
@@ -185,7 +197,7 @@ function semverLt(a, b) {
185
197
  }
186
198
 
187
199
  // src/core/hookFile.ts
188
- var here = dirname(fileURLToPath(import.meta.url));
200
+ var here = dirname2(fileURLToPath(import.meta.url));
189
201
  function packagedHookPath() {
190
202
  const candidates = [
191
203
  join2(here, "hook.cjs"),
@@ -212,7 +224,7 @@ function packageVersion() {
212
224
  return "0.0.0-dev";
213
225
  }
214
226
  function hookVersionPath(target = installedHookPath()) {
215
- return join2(dirname(target), "hook-version");
227
+ return join2(dirname2(target), "hook-version");
216
228
  }
217
229
  function installedHookVersion(target = installedHookPath()) {
218
230
  try {
@@ -235,20 +247,20 @@ function syncHookFile(opts = {}) {
235
247
  }
236
248
  const SIBLINGS = ["gate.cjs", "ocTranslate.mjs", "opencode.mjs", "deskbuddy-dsh.mjs", "wake.cjs", "launcher.cjs"];
237
249
  const siblingMissing = () => SIBLINGS.some((name) => {
238
- if (existsSync2(join2(dirname(target), name))) return false;
250
+ if (existsSync2(join2(dirname2(target), name))) return false;
239
251
  try {
240
- return existsSync2(join2(dirname(opts.source ?? packagedHookPath()), name));
252
+ return existsSync2(join2(dirname2(opts.source ?? packagedHookPath()), name));
241
253
  } catch {
242
254
  return false;
243
255
  }
244
256
  });
245
257
  if (installedHookVersion(target) === version && existsSync2(target) && !siblingMissing()) return false;
246
258
  const source = opts.source ?? packagedHookPath();
247
- mkdirSync2(dirname(target), { recursive: true });
259
+ mkdirSync2(dirname2(target), { recursive: true });
248
260
  atomicCopy(source, target);
249
261
  for (const name of SIBLINGS) {
250
- const from = join2(dirname(source), name);
251
- if (existsSync2(from)) atomicCopy(from, join2(dirname(target), name));
262
+ const from = join2(dirname2(source), name);
263
+ if (existsSync2(from)) atomicCopy(from, join2(dirname2(target), name));
252
264
  }
253
265
  writeFileSync2(hookVersionPath(target), version, "utf8");
254
266
  return true;
@@ -416,7 +428,7 @@ var api = {
416
428
  // src/core/versions.ts
417
429
  import { execFileSync as execFileSync2 } from "child_process";
418
430
  import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync4, renameSync as renameSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
419
- import { basename, dirname as dirname2, join as join3, relative, resolve, sep } from "path";
431
+ import { basename, dirname as dirname3, join as join3, relative, resolve, sep } from "path";
420
432
  function versionsDir(home = deskbuddyHome()) {
421
433
  return join3(home, "versions");
422
434
  }
@@ -440,7 +452,7 @@ function versionCliPath(version, home) {
440
452
  return join3(versionDir(version, home), "node_modules", "deskbuddy", "dist", "cli.js");
441
453
  }
442
454
  function atomicWriteJson(path, value) {
443
- mkdirSync3(dirname2(path), { recursive: true });
455
+ mkdirSync3(dirname3(path), { recursive: true });
444
456
  const tmp = `${path}.${process.pid}.tmp`;
445
457
  writeFileSync3(tmp, JSON.stringify(value, null, 2), "utf8");
446
458
  renameSync3(tmp, path);
@@ -560,7 +572,7 @@ function findDepDir(name, fromDir) {
560
572
  for (; ; ) {
561
573
  const c = join3(d, "node_modules", name);
562
574
  if (existsSync3(join3(c, "package.json"))) return c;
563
- const up = dirname2(d);
575
+ const up = dirname3(d);
564
576
  if (up === d) return null;
565
577
  d = up;
566
578
  }
@@ -578,7 +590,7 @@ function copyRunningCli(pkgRoot, version, home) {
578
590
  recursive: true,
579
591
  dereference: true,
580
592
  // 包自己的嵌套 node_modules 不整个搬:依赖由下面的递归平铺
581
- filter: (src) => !(src !== from && basename(src) === "node_modules" && dirname2(src) === from)
593
+ filter: (src) => !(src !== from && basename(src) === "node_modules" && dirname3(src) === from)
582
594
  });
583
595
  };
584
596
  copyPkg("deskbuddy", pkgRoot);
@@ -617,8 +629,8 @@ function registerRunningCli(running, deps2 = {}) {
617
629
  let how = "already";
618
630
  let note;
619
631
  if (versionDirOf(cliPath, home) !== running.version) {
620
- const distDir = dirname2(cliPath);
621
- const pkgRoot = dirname2(distDir);
632
+ const distDir = dirname3(cliPath);
633
+ const pkgRoot = dirname3(distDir);
622
634
  const pkg = readPkg(pkgRoot);
623
635
  const copyable = basename(distDir) === "dist" && pkg?.name === "deskbuddy" && pkg.version === running.version && !existsSync3(join3(pkgRoot, "src"));
624
636
  how = "inPlace";
@@ -664,7 +676,7 @@ import { execFile, spawn } from "child_process";
664
676
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
665
677
  import { homedir as homedir2 } from "os";
666
678
  import { fileURLToPath as fileURLToPath2 } from "url";
667
- import { basename as basename2, dirname as dirname3, join as join4, resolve as resolve2 } from "path";
679
+ import { basename as basename2, dirname as dirname4, join as join4, resolve as resolve2 } from "path";
668
680
  import { promisify } from "util";
669
681
 
670
682
  // src/core/winproc.ts
@@ -891,7 +903,7 @@ function systemdUnitPath(home = homedir2()) {
891
903
  return join4(home, ".config", "systemd", "user", SYSTEMD_UNIT);
892
904
  }
893
905
  function write(path, content) {
894
- mkdirSync4(dirname3(path), { recursive: true });
906
+ mkdirSync4(dirname4(path), { recursive: true });
895
907
  writeFileSync4(path, content, "utf8");
896
908
  }
897
909
  var BOOTSTRAP_RETRIES = 10;
@@ -963,7 +975,7 @@ async function installAutostart(t, platform = process.platform, opts = {}) {
963
975
  }
964
976
  if (platform === "win32") {
965
977
  const p = winStartupVbsPath();
966
- mkdirSync4(dirname3(p), { recursive: true });
978
+ mkdirSync4(dirname4(p), { recursive: true });
967
979
  writeFileSync4(p, "\uFEFF" + winStartupVbs(t), "utf16le");
968
980
  await run("schtasks", ["/delete", "/f", "/tn", SCHTASK_NAME], { windowsHide: true }).catch(() => {
969
981
  });
@@ -1083,24 +1095,24 @@ function autostartTarget(out = () => {
1083
1095
  // src/daemon/autoUpdate.ts
1084
1096
  import { execFile as execFile2, execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
1085
1097
  import { closeSync, existsSync as existsSync6, mkdirSync as mkdirSync5, openSync, rmSync as rmSync3 } from "fs";
1086
- import { dirname as dirname5, join as join6, posix, win32 } from "path";
1098
+ import { dirname as dirname6, join as join6, posix, win32 } from "path";
1087
1099
 
1088
1100
  // src/wrap/pty.ts
1089
1101
  import { chmodSync, existsSync as existsSync5, statSync } from "fs";
1090
1102
  import { createRequire as createRequire2 } from "module";
1091
- import { dirname as dirname4, join as join5 } from "path";
1103
+ import { dirname as dirname5, join as join5 } from "path";
1092
1104
  import { fileURLToPath as fileURLToPath3 } from "url";
1093
1105
  var req = createRequire2(import.meta.url);
1094
1106
  var VENDORED_PTY_PLATFORMS = ["darwin-arm64", "darwin-x64", "win32-arm64", "win32-x64"];
1095
1107
  function hasVendoredPty(platform = process.platform, arch = process.arch) {
1096
1108
  return VENDORED_PTY_PLATFORMS.includes(`${platform}-${arch}`);
1097
1109
  }
1098
- function vendoredPtyDir(from = dirname4(fileURLToPath3(import.meta.url))) {
1110
+ function vendoredPtyDir(from = dirname5(fileURLToPath3(import.meta.url))) {
1099
1111
  let dir = from;
1100
1112
  for (let i = 0; i < 4; i++) {
1101
1113
  const cand = join5(dir, "vendor", "node-pty");
1102
1114
  if (existsSync5(join5(cand, "package.json"))) return cand;
1103
- const up = dirname4(dir);
1115
+ const up = dirname5(dir);
1104
1116
  if (up === dir) break;
1105
1117
  dir = up;
1106
1118
  }
@@ -1130,7 +1142,7 @@ function loadPty() {
1130
1142
  }
1131
1143
  }
1132
1144
  try {
1133
- const dir = dirname4(req.resolve("node-pty/package.json"));
1145
+ const dir = dirname5(req.resolve("node-pty/package.json"));
1134
1146
  fixSpawnHelper(dir);
1135
1147
  return asPty(req(dir));
1136
1148
  } catch {
@@ -1365,7 +1377,7 @@ function switchFromEnv(env = process.env) {
1365
1377
  function spawnSwitch(cliPath, logFile, from, onExit, execPath = process.execPath) {
1366
1378
  let fd = "ignore";
1367
1379
  try {
1368
- mkdirSync5(dirname5(logFile), { recursive: true });
1380
+ mkdirSync5(dirname6(logFile), { recursive: true });
1369
1381
  fd = openSync(logFile, "a");
1370
1382
  } catch {
1371
1383
  fd = "ignore";
@@ -1504,13 +1516,13 @@ import { readdirSync as readdirSync4 } from "fs";
1504
1516
 
1505
1517
  // src/core/detect.ts
1506
1518
  import { existsSync as existsSync8 } from "fs";
1507
- import { dirname as dirname7 } from "path";
1519
+ import { dirname as dirname8 } from "path";
1508
1520
 
1509
1521
  // src/daemon/locate.ts
1510
1522
  import { execFile as execFile3, execFileSync as execFileSync5 } from "child_process";
1511
1523
  import { accessSync, closeSync as closeSync2, constants, existsSync as existsSync7, lstatSync, openSync as openSync2, readFileSync as readFileSync6, readSync, realpathSync, statSync as statSync2 } from "fs";
1512
1524
  import { homedir as homedir3 } from "os";
1513
- import { delimiter, dirname as dirname6, join as join7, win32 as win322 } from "path";
1525
+ import { delimiter, dirname as dirname7, join as join7, win32 as win322 } from "path";
1514
1526
 
1515
1527
  // src/core/agentEnv.ts
1516
1528
  var BUN_HIJACK_ENV = ["BUN_BE_BUN", "BUN_OPTIONS"];
@@ -1579,7 +1591,8 @@ var defaultLocateDeps = () => ({
1579
1591
  if (fd !== null) closeSync2(fd);
1580
1592
  }
1581
1593
  },
1582
- versionOf: (loc) => probeVersion(loc)
1594
+ versionOf: (loc) => probeVersion(loc),
1595
+ realpath: (p) => realpathSync(p)
1583
1596
  });
1584
1597
  var PROBE_TIMEOUT_MS = 3e3;
1585
1598
  var PROBE_SHORT_TTL_MS = 5 * 60 * 1e3;
@@ -1732,7 +1745,7 @@ function extraBinDirs(home, nodePath = "", win) {
1732
1745
  join7(home, ".bun", "bin"),
1733
1746
  ...win.localAppData ? [join7(win.localAppData, "Volta", "bin")] : [],
1734
1747
  join7(home, "scoop", "shims"),
1735
- ...nodePath ? [dirname6(nodePath)] : []
1748
+ ...nodePath ? [dirname7(nodePath)] : []
1736
1749
  ];
1737
1750
  }
1738
1751
  return [
@@ -1743,7 +1756,7 @@ function extraBinDirs(home, nodePath = "", win) {
1743
1756
  join7(home, ".volta", "bin"),
1744
1757
  "/opt/homebrew/bin",
1745
1758
  "/usr/local/bin",
1746
- ...nodePath ? [dirname6(nodePath)] : []
1759
+ ...nodePath ? [dirname7(nodePath)] : []
1747
1760
  ];
1748
1761
  }
1749
1762
  function whichIn(name, d) {
@@ -1778,7 +1791,7 @@ function binDirs(d) {
1778
1791
  function parseNpmCmdShim(shimPath, text) {
1779
1792
  const m = text.match(/"%~?dp0%?\\?([^"%]+)"\s+%\*/i);
1780
1793
  if (!m) return null;
1781
- return join7(dirname6(shimPath), m[1].replace(/^\\/, ""));
1794
+ return join7(dirname7(shimPath), m[1].replace(/^\\/, ""));
1782
1795
  }
1783
1796
  var JS_RUNTIME_EXE = /^(?:node|bun|bunx|deno)(?:\.exe)?$/i;
1784
1797
  function resolveWinLaunch(file, d) {
@@ -1855,21 +1868,8 @@ function locateWin(tool, d) {
1855
1868
  return pickWinClaude(d);
1856
1869
  case "codex":
1857
1870
  return plain("codex");
1858
- case "workbuddy": {
1859
- const inApp = (dirs) => {
1860
- for (const dir of dirs) {
1861
- for (const ext of [".exe", ".cmd", ""]) {
1862
- const p = join7(dir, WORKBUDDY_CLI_IN_WIN_APP + ext);
1863
- if (d.exists(p)) {
1864
- const r = resolveWinLaunch(p, d);
1865
- if (r) return r;
1866
- }
1867
- }
1868
- }
1869
- return null;
1870
- };
1871
- return inApp(winAppDirs(d, "WorkBuddy")) ?? plain("codebuddy") ?? inApp(d.winAppHints?.("WorkBuddy", "WorkBuddy.exe") ?? []);
1872
- }
1871
+ case "workbuddy":
1872
+ return locateWorkbuddy(d)?.loc ?? null;
1873
1873
  case "qoder_cn": {
1874
1874
  for (const dir of winAppDirs(d, "Qoder CN")) {
1875
1875
  const p = join7(dir, QODER_CN_WORKER_IN_WIN_APP);
@@ -1907,20 +1907,70 @@ function workbuddyHasTui(loc, exists = existsSync7, realpath = realpathSync) {
1907
1907
  const cands = [...loc.prefixArgs, loc.file].map(real2);
1908
1908
  const script = cands.find((a) => BIN.test(a));
1909
1909
  if (!script) return true;
1910
- const dist = join7(dirname6(dirname6(script)), "dist");
1910
+ const dist = join7(dirname7(dirname7(script)), "dist");
1911
1911
  return exists(join7(dist, "codebuddy.js"));
1912
1912
  }
1913
1913
  function locateWorkbuddyTui(d = defaultLocateDeps(), exists, realpath) {
1914
- const cands = [];
1915
- const app = locate("workbuddy", d);
1916
- if (app) cands.push(app);
1917
- const onPath = whichIn("codebuddy", d);
1918
- if (onPath) {
1919
- const loc = isWin(d) ? resolveWinLaunch(onPath, d) : { file: onPath, prefixArgs: [] };
1920
- if (loc && !cands.some((c) => c.file === loc.file && c.prefixArgs.join("\0") === loc.prefixArgs.join("\0"))) cands.push(loc);
1921
- }
1914
+ const cands = [locateWorkbuddyStandalone(d), locateWorkbuddyApp(d)].filter((c) => c !== null);
1922
1915
  return cands.find((c) => workbuddyHasTui(c, exists, realpath)) ?? null;
1923
1916
  }
1917
+ function locateQoderCnTui(d = defaultLocateDeps()) {
1918
+ if (isWin(d)) return locate("qoder_cn", d);
1919
+ const onPath = whichIn("qoderclicn", d);
1920
+ if (onPath) return { file: onPath, prefixArgs: [] };
1921
+ return locate("qoder_cn", d);
1922
+ }
1923
+ var WORKBUDDY_STANDALONE_PKG = "@tencent-ai/codebuddy-code";
1924
+ function isAppBundledPath(p) {
1925
+ return /app\.asar(?:\.unpacked)?[\\/]/i.test(p) || /\.app[\\/]Contents[\\/]/.test(p);
1926
+ }
1927
+ function locateWorkbuddyStandalone(d = defaultLocateDeps()) {
1928
+ const real2 = (p) => {
1929
+ try {
1930
+ return d.realpath ? d.realpath(p) : p;
1931
+ } catch {
1932
+ return p;
1933
+ }
1934
+ };
1935
+ for (const f of whichAllIn("codebuddy", d)) {
1936
+ if (isWin(d)) {
1937
+ const loc = resolveWinLaunch(f, d);
1938
+ if (!loc || [loc.file, ...loc.prefixArgs].some(isAppBundledPath)) continue;
1939
+ return loc;
1940
+ }
1941
+ if (isAppBundledPath(f) || isAppBundledPath(real2(f))) continue;
1942
+ return { file: f, prefixArgs: [] };
1943
+ }
1944
+ return null;
1945
+ }
1946
+ function locateWorkbuddyApp(d = defaultLocateDeps()) {
1947
+ if (!isWin(d)) {
1948
+ for (const dir of appDirs(d.home)) {
1949
+ const p = join7(dir, WORKBUDDY_CLI_IN_APP);
1950
+ if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
1951
+ }
1952
+ return null;
1953
+ }
1954
+ const inApp = (dirs) => {
1955
+ for (const dir of dirs) {
1956
+ for (const ext of [".exe", ".cmd", ""]) {
1957
+ const p = join7(dir, WORKBUDDY_CLI_IN_WIN_APP + ext);
1958
+ if (d.exists(p)) {
1959
+ const r = resolveWinLaunch(p, d);
1960
+ if (r) return r;
1961
+ }
1962
+ }
1963
+ }
1964
+ return null;
1965
+ };
1966
+ return inApp(winAppDirs(d, "WorkBuddy")) ?? inApp(d.winAppHints?.("WorkBuddy", "WorkBuddy.exe") ?? []);
1967
+ }
1968
+ function locateWorkbuddy(d = defaultLocateDeps()) {
1969
+ const s2 = locateWorkbuddyStandalone(d);
1970
+ if (s2) return { loc: s2, kind: "standalone" };
1971
+ const a = locateWorkbuddyApp(d);
1972
+ return a ? { loc: a, kind: "app" } : null;
1973
+ }
1924
1974
  var WORKBUDDY_CLI_IN_APP = "WorkBuddy.app/Contents/Resources/app.asar.unpacked/cli/bin/codebuddy";
1925
1975
  var QODER_CN_WORKER_IN_APP = "Qoder CN.app/Contents/Resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-cn-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
1926
1976
  var QODER_WORKER_IN_APP = "Qoder.app/Contents/Resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
@@ -1935,13 +1985,8 @@ function locate(tool, d = defaultLocateDeps()) {
1935
1985
  return plain("claude");
1936
1986
  case "codex":
1937
1987
  return plain("codex");
1938
- case "workbuddy": {
1939
- for (const dir of appDirs(d.home)) {
1940
- const p = join7(dir, WORKBUDDY_CLI_IN_APP);
1941
- if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
1942
- }
1943
- return plain("codebuddy");
1944
- }
1988
+ case "workbuddy":
1989
+ return locateWorkbuddy(d)?.loc ?? null;
1945
1990
  case "qoder_cn": {
1946
1991
  for (const dir of appDirs(d.home)) {
1947
1992
  const p = join7(dir, QODER_CN_WORKER_IN_APP);
@@ -2000,13 +2045,13 @@ function toolConfigDir(tool) {
2000
2045
  case "codex":
2001
2046
  return codexHome();
2002
2047
  case "workbuddy":
2003
- return dirname7(workbuddySettingsPath());
2048
+ return dirname8(workbuddySettingsPath());
2004
2049
  case "qoder_cn":
2005
- return dirname7(qoderCnSettingsPath());
2050
+ return dirname8(qoderCnSettingsPath());
2006
2051
  case "qoder":
2007
- return dirname7(qoderSettingsPath());
2052
+ return dirname8(qoderSettingsPath());
2008
2053
  case "opencode":
2009
- return dirname7(dirname7(opencodePluginPath()));
2054
+ return dirname8(dirname8(opencodePluginPath()));
2010
2055
  case "qwenwork":
2011
2056
  return qwenworkHome();
2012
2057
  case "deepseek_harness":
@@ -2222,11 +2267,11 @@ async function refreshRegistry() {
2222
2267
  }
2223
2268
 
2224
2269
  // src/commands/enable.ts
2225
- import { dirname as dirname13, join as join20 } from "path";
2270
+ import { dirname as dirname15, join as join21 } from "path";
2226
2271
 
2227
2272
  // src/adapters/ccHook.ts
2228
2273
  import { chmodSync as chmodSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
2229
- import { dirname as dirname8, join as join9 } from "path";
2274
+ import { dirname as dirname9, join as join9 } from "path";
2230
2275
 
2231
2276
  // src/daemon/gate/settings.ts
2232
2277
  import { existsSync as existsSync9 } from "fs";
@@ -2316,7 +2361,7 @@ function readSettings(path) {
2316
2361
  }
2317
2362
  }
2318
2363
  function writeSettings(path, settings) {
2319
- mkdirSync7(dirname8(path), { recursive: true });
2364
+ mkdirSync7(dirname9(path), { recursive: true });
2320
2365
  let mode;
2321
2366
  try {
2322
2367
  mode = statSync3(path).mode & 511;
@@ -2448,8 +2493,8 @@ function retagCcHook(settingsPath, scripts, from, to) {
2448
2493
  function uninstallCcHook(settingsPath, hookPath) {
2449
2494
  const settings = readSettings(settingsPath);
2450
2495
  if (!settings.hooks) return;
2451
- const gatePath = join9(dirname8(hookPath), "gate.cjs");
2452
- const wakePath = join9(dirname8(hookPath), "wake.cjs");
2496
+ const gatePath = join9(dirname9(hookPath), "gate.cjs");
2497
+ const wakePath = join9(dirname9(hookPath), "wake.cjs");
2453
2498
  for (const event of Object.keys(settings.hooks)) {
2454
2499
  const groups = settings.hooks[event] ?? [];
2455
2500
  const kept = [];
@@ -2463,14 +2508,14 @@ function uninstallCcHook(settingsPath, hookPath) {
2463
2508
  }
2464
2509
  writeSettings(settingsPath, settings);
2465
2510
  try {
2466
- rmSync4(join9(dirname8(hookPath), "wake", "tokens"), { recursive: true, force: true });
2511
+ rmSync4(join9(dirname9(hookPath), "wake", "tokens"), { recursive: true, force: true });
2467
2512
  } catch {
2468
2513
  }
2469
2514
  }
2470
2515
 
2471
2516
  // src/adapters/codexHook.ts
2472
2517
  import { chmodSync as chmodSync3, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, renameSync as renameSync5, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
2473
- import { dirname as dirname9, join as join10 } from "path";
2518
+ import { dirname as dirname10, join as join10 } from "path";
2474
2519
  var CODEX_MARKER_BEGIN = "# >>> deskbuddy >>>";
2475
2520
  var CODEX_MARKER_END = "# <<< deskbuddy <<<";
2476
2521
  var CODEX_EVENTS = [
@@ -2490,19 +2535,19 @@ function renderBlock(hookPath, events, tool, platform) {
2490
2535
  const script = platform === "win32" ? winPsPath(hookPath) : hookPath;
2491
2536
  const command = JSON.stringify(`node ${script} ${tool}`);
2492
2537
  const appended = JSON.stringify(`node ${script} ${tool} --appended`);
2493
- const lines2 = [CODEX_MARKER_BEGIN];
2538
+ const lines3 = [CODEX_MARKER_BEGIN];
2494
2539
  for (const event of events) {
2495
- lines2.push(`[[hooks.${event}]]`, 'matcher = ""', "hooks = [", ` { type = "command", command = ${command}, async = true, timeoutSec = 5 }`);
2540
+ lines3.push(`[[hooks.${event}]]`, 'matcher = ""', "hooks = [", ` { type = "command", command = ${command}, async = true, timeoutSec = 5 }`);
2496
2541
  if (event === "UserPromptSubmit") {
2497
- lines2.push(` , { type = "command", command = ${appended}, async = false, timeout = ${CODEX_APPENDED_TIMEOUT_S} }`);
2542
+ lines3.push(` , { type = "command", command = ${appended}, async = false, timeout = ${CODEX_APPENDED_TIMEOUT_S} }`);
2498
2543
  }
2499
- lines2.push("]", "");
2544
+ lines3.push("]", "");
2500
2545
  }
2501
- lines2.push(CODEX_MARKER_END);
2502
- return lines2.join("\n");
2546
+ lines3.push(CODEX_MARKER_END);
2547
+ return lines3.join("\n");
2503
2548
  }
2504
2549
  function writeAtomic(path, content) {
2505
- mkdirSync8(dirname9(path), { recursive: true });
2550
+ mkdirSync8(dirname10(path), { recursive: true });
2506
2551
  let mode;
2507
2552
  try {
2508
2553
  mode = statSync4(path).mode & 511;
@@ -2525,13 +2570,13 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
2525
2570
  const raw = existsSync11(configPath2) ? readFileSync9(configPath2, "utf8") : "";
2526
2571
  const block = renderBlock(hookPath, events, tool, platform);
2527
2572
  if (raw.includes(CODEX_MARKER_BEGIN)) {
2528
- const lines2 = raw.split("\n");
2529
- const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
2530
- const end = lines2.findIndex((l, i) => i > begin && l.trim() === CODEX_MARKER_END);
2573
+ const lines3 = raw.split("\n");
2574
+ const begin = lines3.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
2575
+ const end = lines3.findIndex((l, i) => i > begin && l.trim() === CODEX_MARKER_END);
2531
2576
  if (begin === -1 || end === -1) return;
2532
- if (lines2.slice(begin, end + 1).join("\n") === block) return;
2533
- lines2.splice(begin, end - begin + 1, ...block.split("\n"));
2534
- writeAtomic(configPath2, lines2.join("\n"));
2577
+ if (lines3.slice(begin, end + 1).join("\n") === block) return;
2578
+ lines3.splice(begin, end - begin + 1, ...block.split("\n"));
2579
+ writeAtomic(configPath2, lines3.join("\n"));
2535
2580
  return;
2536
2581
  }
2537
2582
  assertSafeToAppend(raw, configPath2);
@@ -2541,21 +2586,21 @@ function installCodexHook(configPath2, hookPath, events = CODEX_EVENTS, tool = "
2541
2586
  function uninstallCodexHook(configPath2) {
2542
2587
  if (!existsSync11(configPath2)) return;
2543
2588
  const raw = readFileSync9(configPath2, "utf8");
2544
- const lines2 = raw.split("\n");
2545
- const begin = lines2.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
2589
+ const lines3 = raw.split("\n");
2590
+ const begin = lines3.findIndex((l) => l.trim() === CODEX_MARKER_BEGIN);
2546
2591
  if (begin === -1) return;
2547
- const end = lines2.findIndex((l, i) => i > begin && l.trim() === CODEX_MARKER_END);
2592
+ const end = lines3.findIndex((l, i) => i > begin && l.trim() === CODEX_MARKER_END);
2548
2593
  if (end === -1) {
2549
2594
  throw new Error(`${configPath2} \u91CC\u53EA\u627E\u5230\u5F00\u59CB\u6807\u8BB0\u3001\u6CA1\u6709\u7ED3\u675F\u6807\u8BB0\uFF0C\u4E3A\u514D\u8BEF\u5220\u5DF2\u4E2D\u6B62\uFF0C\u8BF7\u624B\u5DE5\u68C0\u67E5`);
2550
2595
  }
2551
- const start = begin > 0 && lines2[begin - 1].trim() === "" ? begin - 1 : begin;
2552
- lines2.splice(start, end - start + 1);
2553
- writeAtomic(configPath2, lines2.join("\n"));
2596
+ const start = begin > 0 && lines3[begin - 1].trim() === "" ? begin - 1 : begin;
2597
+ lines3.splice(start, end - start + 1);
2598
+ writeAtomic(configPath2, lines3.join("\n"));
2554
2599
  }
2555
2600
 
2556
2601
  // src/adapters/ocPlugin.ts
2557
2602
  import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync10, renameSync as renameSync6, unlinkSync, writeFileSync as writeFileSync8 } from "fs";
2558
- import { dirname as dirname10, join as join11 } from "path";
2603
+ import { dirname as dirname11, join as join11 } from "path";
2559
2604
  import { pathToFileURL } from "url";
2560
2605
  var OC_MARKER = "// deskbuddy:opencode-plugin";
2561
2606
  function ocPluginSource(pluginModule, nodePath, platform = process.platform) {
@@ -2569,13 +2614,13 @@ function ocPluginSource(pluginModule, nodePath, platform = process.platform) {
2569
2614
  ].join("\n");
2570
2615
  }
2571
2616
  function ocPluginModule(hookPath) {
2572
- return join11(dirname10(hookPath), "opencode.mjs");
2617
+ return join11(dirname11(hookPath), "opencode.mjs");
2573
2618
  }
2574
2619
  function installOcPlugin(pluginPath, hookPath, nodePath) {
2575
2620
  if (existsSync12(pluginPath) && !readFileSync10(pluginPath, "utf8").includes(OC_MARKER)) {
2576
2621
  throw new Error(`${pluginPath} \u5DF2\u5B58\u5728\u4E14\u4E0D\u662F DeskBuddy \u751F\u6210\u7684\uFF0C\u8BF7\u5148\u632A\u8D70\u518D\u91CD\u8BD5`);
2577
2622
  }
2578
- mkdirSync9(dirname10(pluginPath), { recursive: true });
2623
+ mkdirSync9(dirname11(pluginPath), { recursive: true });
2579
2624
  const tmp = pluginPath + ".deskbuddy.tmp";
2580
2625
  writeFileSync8(tmp, ocPluginSource(ocPluginModule(hookPath), nodePath), "utf8");
2581
2626
  renameSync6(tmp, pluginPath);
@@ -2597,13 +2642,13 @@ var OC_NOTICE = " \u5DF2\u5F00\u7740\u7684 opencode \u7A97\u53E3\u91CD\u5F00\u
2597
2642
 
2598
2643
  // src/adapters/dshPlugin.ts
2599
2644
  import { copyFileSync as copyFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync11, renameSync as renameSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
2600
- import { dirname as dirname11, join as join12 } from "path";
2645
+ import { dirname as dirname12, join as join12 } from "path";
2601
2646
  var DSH_MARKER_BEGIN = "# >>> deskbuddy:dsh-plugin";
2602
2647
  var DSH_MARKER_END = "# <<< deskbuddy:dsh-plugin";
2603
2648
  var CREATED_FLAG = "created-file";
2604
2649
  var DSH_ENTRY_ID = "deskbuddy-cli";
2605
2650
  function dshPluginModule(hookPath) {
2606
- return join12(dirname11(hookPath), "deskbuddy-dsh.mjs");
2651
+ return join12(dirname12(hookPath), "deskbuddy-dsh.mjs");
2607
2652
  }
2608
2653
  function dshBlock(pluginModule, nodePath, created) {
2609
2654
  return [
@@ -2618,13 +2663,13 @@ function dshBlock(pluginModule, nodePath, created) {
2618
2663
  ].join("\n");
2619
2664
  }
2620
2665
  function splitOurs(text) {
2621
- const lines2 = text.split("\n");
2622
- const b = lines2.findIndex((l) => l.startsWith(DSH_MARKER_BEGIN));
2666
+ const lines3 = text.split("\n");
2667
+ const b = lines3.findIndex((l) => l.startsWith(DSH_MARKER_BEGIN));
2623
2668
  if (b < 0) return { rest: text, found: false, created: false };
2624
- const e = lines2.findIndex((l, i) => i > b && l.startsWith(DSH_MARKER_END));
2669
+ const e = lines3.findIndex((l, i) => i > b && l.startsWith(DSH_MARKER_END));
2625
2670
  if (e < 0) return { rest: text, found: false, created: false, broken: true };
2626
- const created = lines2[b].slice(DSH_MARKER_BEGIN.length).includes(CREATED_FLAG);
2627
- return { rest: [...lines2.slice(0, b), ...lines2.slice(e + 1)].join("\n"), found: true, created };
2671
+ const created = lines3[b].slice(DSH_MARKER_BEGIN.length).includes(CREATED_FLAG);
2672
+ return { rest: [...lines3.slice(0, b), ...lines3.slice(e + 1)].join("\n"), found: true, created };
2628
2673
  }
2629
2674
  function significant(text) {
2630
2675
  return text.split("\n").filter((l) => l.trim() !== "" && !l.trimStart().startsWith("#"));
@@ -2642,7 +2687,7 @@ function patchShape(text) {
2642
2687
  return "block";
2643
2688
  }
2644
2689
  function atomicWrite(path, text) {
2645
- mkdirSync10(dirname11(path), { recursive: true });
2690
+ mkdirSync10(dirname12(path), { recursive: true });
2646
2691
  const tmp = `${path}.deskbuddy.tmp`;
2647
2692
  writeFileSync9(tmp, text, "utf8");
2648
2693
  renameSync7(tmp, path);
@@ -3149,12 +3194,12 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
3149
3194
  }
3150
3195
 
3151
3196
  // src/commands/migrateQoder.ts
3152
- import { dirname as dirname12, join as join14 } from "path";
3197
+ import { dirname as dirname13, join as join14 } from "path";
3153
3198
  function migrateLegacyQoder(out, detect = () => detectTools()) {
3154
3199
  try {
3155
3200
  const cfg = loadConfig();
3156
3201
  const hookPath = installedHookPath();
3157
- const scripts = [hookPath, join14(dirname12(hookPath), "gate.cjs")];
3202
+ const scripts = [hookPath, join14(dirname13(hookPath), "gate.cjs")];
3158
3203
  if (!retagCcHook(qoderCnSettingsPath(), scripts, "qoder", "qoder_cn")) return false;
3159
3204
  const hadQoder = cfg.enabledTools.includes("qoder");
3160
3205
  let intl = false;
@@ -3183,7 +3228,7 @@ function migrateLegacyQoder(out, detect = () => detectTools()) {
3183
3228
 
3184
3229
  // src/daemon/loginGuide.ts
3185
3230
  import { execFile as execFile6, execFileSync as execFileSync10 } from "child_process";
3186
- import { chmodSync as chmodSync7, mkdirSync as mkdirSync15, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "fs";
3231
+ import { chmodSync as chmodSync7, existsSync as existsSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "fs";
3187
3232
  import { homedir as homedir6 } from "os";
3188
3233
  import { join as join19 } from "path";
3189
3234
 
@@ -3347,13 +3392,13 @@ function analyzeTranscript(file) {
3347
3392
  const buf = Buffer.alloc(len);
3348
3393
  fd = openSync3(file, "r");
3349
3394
  readSync2(fd, buf, 0, len, size - len);
3350
- const lines2 = buf.toString("utf8").split("\n");
3395
+ const lines3 = buf.toString("utf8").split("\n");
3351
3396
  let seg = false;
3352
3397
  let reply = null;
3353
3398
  let humanAfter = false;
3354
3399
  let interrupted = null;
3355
- for (let i = lines2.length - 1; i >= 0; i--) {
3356
- const line = lines2[i];
3400
+ for (let i = lines3.length - 1; i >= 0; i--) {
3401
+ const line = lines3[i];
3357
3402
  if (line.indexOf('"assistant"') === -1 && line.indexOf('"user"') === -1) continue;
3358
3403
  let o;
3359
3404
  try {
@@ -3397,7 +3442,7 @@ function analyzeTranscript(file) {
3397
3442
  // src/core/launch.ts
3398
3443
  var RUN_ID_RE = /^[a-f0-9]{32}$/;
3399
3444
  var SESSION_UUID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
3400
- var LAUNCH_TOOLS = ["claude_code", "codex", "workbuddy"];
3445
+ var LAUNCH_TOOLS = ["claude_code", "codex", "workbuddy", "qoder_cn"];
3401
3446
  function validPlan(p) {
3402
3447
  if (!LAUNCH_TOOLS.includes(p.tool)) return false;
3403
3448
  if (p.resume && (p.tool !== "claude_code" || !SESSION_UUID_RE.test(p.resume))) return false;
@@ -3538,6 +3583,7 @@ function buildSpawnPlan(cmd, cwd, gate, home = homedir5()) {
3538
3583
  return { file, args: workbuddyPlan(cmd, gate), cwd, env: { CODEBUDDY_CONFIG_DIR: join17(home, ".workbuddy") } };
3539
3584
  case "qoder_cn":
3540
3585
  case "qoder":
3586
+ if (cmd.sessionId && !SESSION_UUID_RE.test(cmd.sessionId)) return null;
3541
3587
  return gate ? { file, args: qoderPlan(cmd, gate), cwd } : null;
3542
3588
  case "opencode":
3543
3589
  return gate ? {
@@ -3688,9 +3734,9 @@ function finalResultOf(stdout7) {
3688
3734
  return pick(JSON.parse(stdout7));
3689
3735
  } catch {
3690
3736
  }
3691
- const lines2 = stdout7.split("\n");
3692
- for (let i = lines2.length - 1; i >= 0; i--) {
3693
- const l = lines2[i].trim();
3737
+ const lines3 = stdout7.split("\n");
3738
+ for (let i = lines3.length - 1; i >= 0; i--) {
3739
+ const l = lines3[i].trim();
3694
3740
  if (!l.startsWith("{")) continue;
3695
3741
  try {
3696
3742
  const o = pick(JSON.parse(l));
@@ -4001,16 +4047,30 @@ ${r.stderr}`;
4001
4047
  return { tool, state: "unknown", note: "\u8BA4\u4E0D\u51FA codex login status \u7684\u8F93\u51FA" };
4002
4048
  }
4003
4049
  var WORKBUDDY_PROBE_ARGS = ["-p", PROBE_TEXT, "--output-format", "json", "--no-session-persistence", "--max-turns", "1"];
4050
+ var WORKBUDDY_AUTH_FILE = "Tencent-Cloud.coding-copilot.info";
4051
+ function workbuddyAuthFile(home, platform, localAppData = "") {
4052
+ const tail2 = ["CodeBuddyExtension", "Data", "Public", "auth", WORKBUDDY_AUTH_FILE];
4053
+ if (platform === "darwin") return join19(home, "Library", "Application Support", ...tail2);
4054
+ if (platform === "win32") return localAppData ? join19(localAppData, ...tail2) : null;
4055
+ return null;
4056
+ }
4004
4057
  async function checkWorkbuddy(d) {
4005
4058
  const tool = "workbuddy";
4006
4059
  const loc = d.locate(tool);
4007
4060
  if (!loc) return { tool, state: "unknown", note: "\u627E\u4E0D\u5230\u53EF\u6267\u884C\u6587\u4EF6" };
4061
+ const kind = d.workbuddyKind?.() ?? null;
4062
+ if (kind === "standalone") {
4063
+ const auth = d.workbuddyAuthFile?.() ?? null;
4064
+ if (auth && d.exists?.(auth)) return { tool, state: "logged_in", note: "\u72EC\u7ACB\u7248\u51ED\u636E\u6587\u4EF6\u5728" };
4065
+ }
4008
4066
  const env = envFor(d, loc, { CODEBUDDY_CONFIG_DIR: join19(d.home, ".workbuddy"), DESKBUDDY_NO_WAKE: "1", [PROBE_ENV]: "1" });
4009
4067
  const r = await d.run(loc.file, [...loc.prefixArgs, ...WORKBUDDY_PROBE_ARGS], env, WORKBUDDY_PROBE_TIMEOUT_MS);
4010
4068
  const bad = failedRun(r);
4011
4069
  if (bad) return { tool, state: "unknown", note: bad };
4012
4070
  const out = { stdout: r.stdout, stderr: r.stderr };
4013
- if (isNotLoggedIn(out, PROBE_TEXT) || r.code === 0 && isNotLoggedInOk(out, PROBE_TEXT, tool)) return { tool, state: "not_logged_in" };
4071
+ if (isNotLoggedIn(out, PROBE_TEXT) || r.code === 0 && isNotLoggedInOk(out, PROBE_TEXT, tool)) {
4072
+ return kind === "app" ? { tool, state: "not_logged_in", note: "\u53EA\u88C5\u4E86 WorkBuddy App\uFF0C\u6CA1\u6709\u72EC\u7ACB\u7248\u547D\u4EE4\u884C" } : { tool, state: "not_logged_in" };
4073
+ }
4014
4074
  if (r.code === 0 && r.stdout.trim()) return { tool, state: "logged_in" };
4015
4075
  return { tool, state: "unknown", note: `\u63A2\u6D4B\u6CA1\u6210\u529F\uFF08\u9000\u51FA\u7801 ${r.code ?? "\u65E0"}\uFF09` };
4016
4076
  }
@@ -4093,7 +4153,10 @@ var defaultCheckDeps = () => ({
4093
4153
  return null;
4094
4154
  }
4095
4155
  },
4096
- platform: process.platform
4156
+ platform: process.platform,
4157
+ workbuddyKind: () => locateWorkbuddy()?.kind ?? null,
4158
+ workbuddyAuthFile: () => workbuddyAuthFile(homedir6(), process.platform, process.env.LOCALAPPDATA ?? ""),
4159
+ exists: (p) => existsSync16(p)
4097
4160
  });
4098
4161
  var LOGIN_ARGS = {
4099
4162
  claude_code: ["auth", "login"],
@@ -4108,6 +4171,8 @@ var defaultCommandDeps = () => ({
4108
4171
  locateWorkbuddyTui: () => locateWorkbuddyTui(),
4109
4172
  home: homedir6()
4110
4173
  });
4174
+ var WORKBUDDY_LOGIN_HINT = "\uFF08\u8F93\u5165 /login \u56DE\u8F66\uFF0C\u9009\u56FD\u5185\u7248\uFF09";
4175
+ var WORKBUDDY_NEED_STANDALONE = "\u53EA\u88C5\u4E86 WorkBuddy App\uFF0C\u5B83\u81EA\u5E26\u7684\u547D\u4EE4\u884C\u7528\u4E0D\u4E86 App \u7684\u767B\u5F55\uFF1B\u8BBE\u5907\u4E0A\u65B0\u5EFA / \u56DE\u590D\u4EFB\u52A1\u9700\u8981\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08\u8FD0\u884C deskbuddy workbuddy-setup \u5B89\u88C5\uFF09";
4111
4176
  function loginCommandFor(tool, d) {
4112
4177
  const extra = LOGIN_ARGS[tool];
4113
4178
  if (!extra) return { error: "unsupported" };
@@ -4118,7 +4183,7 @@ function loginCommandFor(tool, d) {
4118
4183
  loc = d.locateWorkbuddyTui();
4119
4184
  if (!loc) return d.locate(tool) ? { error: "no_tui" } : { error: "not_found" };
4120
4185
  env = { CODEBUDDY_CONFIG_DIR: join19(d.home, ".workbuddy") };
4121
- hint = "\uFF08\u8F93\u5165 /login \u56DE\u8F66\uFF09";
4186
+ hint = WORKBUDDY_LOGIN_HINT;
4122
4187
  } else {
4123
4188
  loc = d.locate(tool);
4124
4189
  if (!loc) return { error: "not_found" };
@@ -4156,21 +4221,21 @@ var shq2 = (s2) => `'${s2}'`;
4156
4221
  var psq2 = (s2) => `'${s2.replace(/'/g, "''")}'`;
4157
4222
  function macLoginScript(cmd, pathDirs = []) {
4158
4223
  checkLoginCommand(cmd);
4159
- const lines2 = ["#!/bin/sh", `printf '%s\\n' ${shq2(bannerLine(cmd))}`, `printf '\\n'`];
4160
- for (const [k, v] of Object.entries(cmd.env)) lines2.push(`export ${k}=${shq2(v)}`);
4224
+ const lines3 = ["#!/bin/sh", `printf '%s\\n' ${shq2(bannerLine(cmd))}`, `printf '\\n'`];
4225
+ for (const [k, v] of Object.entries(cmd.env)) lines3.push(`export ${k}=${shq2(v)}`);
4161
4226
  const dirs = pathDirs.filter((p) => p.startsWith("/") && safePath(p) && !p.includes(":"));
4162
- if (dirs.length > 0) lines2.push(`export PATH=${shq2(dirs.join(":"))}":$PATH"`);
4163
- lines2.push([cmd.file, ...cmd.args].map(shq2).join(" "));
4164
- lines2.push(`printf '\\n%s\\n' ${shq2(DONE_LINE)}`);
4165
- return lines2.join("\n") + "\n";
4227
+ if (dirs.length > 0) lines3.push(`export PATH=${shq2(dirs.join(":"))}":$PATH"`);
4228
+ lines3.push([cmd.file, ...cmd.args].map(shq2).join(" "));
4229
+ lines3.push(`printf '\\n%s\\n' ${shq2(DONE_LINE)}`);
4230
+ return lines3.join("\n") + "\n";
4166
4231
  }
4167
4232
  function winLoginScript(cmd) {
4168
4233
  checkLoginCommand(cmd);
4169
- const lines2 = [`$host.UI.RawUI.WindowTitle = ${psq2(`DeskBuddy - ${cmd.label} \u767B\u5F55`)}`, `Write-Host ${psq2(bannerLine(cmd))}`, `Write-Host ''`];
4170
- for (const [k, v] of Object.entries(cmd.env)) lines2.push(`$env:${k} = ${psq2(v)}`);
4171
- lines2.push(`& ${[cmd.file, ...cmd.args].map(psq2).join(" ")}`);
4172
- lines2.push(`Write-Host ''`, `Write-Host ${psq2(DONE_LINE)}`);
4173
- return lines2.join("\r\n") + "\r\n";
4234
+ const lines3 = [`$host.UI.RawUI.WindowTitle = ${psq2(`DeskBuddy - ${cmd.label} \u767B\u5F55`)}`, `Write-Host ${psq2(bannerLine(cmd))}`, `Write-Host ''`];
4235
+ for (const [k, v] of Object.entries(cmd.env)) lines3.push(`$env:${k} = ${psq2(v)}`);
4236
+ lines3.push(`& ${[cmd.file, ...cmd.args].map(psq2).join(" ")}`);
4237
+ lines3.push(`Write-Host ''`, `Write-Host ${psq2(DONE_LINE)}`);
4238
+ return lines3.join("\r\n") + "\r\n";
4174
4239
  }
4175
4240
  function loginScriptPath(home, tool, platform) {
4176
4241
  return join19(launchDir(home), `login-${tool}.${platform === "win32" ? "ps1" : "command"}`);
@@ -4231,7 +4296,7 @@ async function openGuide(tool, d) {
4231
4296
  if (!gui) return { tool, action: "no_gui", manual };
4232
4297
  try {
4233
4298
  await d.open(cmd);
4234
- return { tool, action: "opened", note: cmd.hint ? `\u5728\u7A97\u53E3\u91CC${cmd.hint.replace(/[()]/g, "")}\u5B8C\u6210\u767B\u5F55` : "" };
4299
+ return { tool, action: "opened", note: cmd.hint ? `\u5728\u7A97\u53E3\u91CC${cmd.hint.replace(/[()]/g, "")}\uFF0C\u5B8C\u6210\u767B\u5F55` : "" };
4235
4300
  } catch (e) {
4236
4301
  return { tool, action: "open_failed", note: e?.message ?? String(e), manual };
4237
4302
  }
@@ -4261,7 +4326,7 @@ function outcomeLine(o) {
4261
4326
  case "open_failed":
4262
4327
  return `\u26A0\uFE0F ${name} \u672A\u767B\u5F55\uFF0C\u6CA1\u80FD\u6253\u5F00\u767B\u5F55\u7A97\u53E3\uFF08${o.note}\uFF09\uFF1A\u8BF7\u5728\u7535\u8111\u7EC8\u7AEF\u8FD0\u884C ${o.manual}`;
4263
4328
  case "no_tui":
4264
- return `\u26A0\uFE0F ${name} \u672A\u767B\u5F55\uFF1A\u672C\u673A ${name} \u81EA\u5E26\u7684\u547D\u4EE4\u884C\u6CA1\u6709\u4EA4\u4E92\u754C\u9762\uFF0C\u6CA1\u6CD5\u5728\u7EC8\u7AEF\u91CC\u767B\u5F55\uFF0C\u8BF7\u8054\u7CFB\u6211\u4EEC`;
4329
+ return o.tool === "workbuddy" ? `\u26A0\uFE0F ${name} \u547D\u4EE4\u884C\u4E0D\u53EF\u7528\uFF1A${WORKBUDDY_NEED_STANDALONE}` : `\u26A0\uFE0F ${name} \u672A\u767B\u5F55\uFF1A\u672C\u673A ${name} \u81EA\u5E26\u7684\u547D\u4EE4\u884C\u6CA1\u6709\u4EA4\u4E92\u754C\u9762\uFF0C\u6CA1\u6CD5\u5728\u7EC8\u7AEF\u91CC\u767B\u5F55\uFF0C\u8BF7\u8054\u7CFB\u6211\u4EEC`;
4265
4330
  default:
4266
4331
  return `\u26A0\uFE0F ${name} \u672A\u767B\u5F55\uFF08\u627E\u4E0D\u5230\u5B83\u7684\u547D\u4EE4\u884C\uFF09`;
4267
4332
  }
@@ -4273,6 +4338,7 @@ function summaryLine(outcomes) {
4273
4338
  if (o.state === "logged_in") return `${name} \u5DF2\u767B\u5F55`;
4274
4339
  if (o.state === "unknown") return `${name} \u672A\u77E5`;
4275
4340
  if (o.action === "opened") return `${name} \u672A\u767B\u5F55\uFF0C\u5DF2\u6253\u5F00\u767B\u5F55\u7A97\u53E3`;
4341
+ if (o.action === "no_tui" && o.tool === "workbuddy") return `${name} \u9700\u8981\u5B89\u88C5\u72EC\u7ACB\u7248\u547D\u4EE4\u884C`;
4276
4342
  return `${name} \u672A\u767B\u5F55\uFF0C\u8BF7\u5728\u7535\u8111\u7EC8\u7AEF\u91CC\u767B\u5F55`;
4277
4343
  };
4278
4344
  return `\u767B\u5F55\u68C0\u67E5: ${outcomes.map(part).join("\uFF1B")}`;
@@ -4336,7 +4402,16 @@ var LoginGuide = class {
4336
4402
  this.#d.log(`${name} \u547D\u4EE4\u884C\u6CA1\u767B\u5F55\uFF0C\u6253\u5F00\u767B\u5F55\u7A97\u53E3\u5931\u8D25\uFF08${r.note}\uFF09\uFF1A\u8BF7\u5728\u7535\u8111\u7EC8\u7AEF\u8FD0\u884C ${r.manual}`);
4337
4403
  break;
4338
4404
  case "no_tui":
4339
- this.#d.log(`${name} \u547D\u4EE4\u884C\u6CA1\u767B\u5F55\uFF0C\u672C\u673A ${name} \u81EA\u5E26\u7684\u547D\u4EE4\u884C\u6CA1\u6709\u4EA4\u4E92\u754C\u9762\uFF0C\u6CA1\u6CD5\u6253\u5F00\u767B\u5F55\u7A97\u53E3`);
4405
+ if (tool === "workbuddy") {
4406
+ this.#d.log(`${name} \u547D\u4EE4\u884C\u8DD1\u4E0D\u8D77\u6765\uFF1A${WORKBUDDY_NEED_STANDALONE}`);
4407
+ try {
4408
+ this.#d.onNeedStandalone?.(tool);
4409
+ } catch (e) {
4410
+ this.#d.log(`${name} \u72EC\u7ACB\u7248\u8BE2\u95EE\u51FA\u9519\uFF1A${e?.message ?? e}`);
4411
+ }
4412
+ } else {
4413
+ this.#d.log(`${name} \u547D\u4EE4\u884C\u6CA1\u767B\u5F55\uFF0C\u672C\u673A ${name} \u81EA\u5E26\u7684\u547D\u4EE4\u884C\u6CA1\u6709\u4EA4\u4E92\u754C\u9762\uFF0C\u6CA1\u6CD5\u6253\u5F00\u767B\u5F55\u7A97\u53E3`);
4414
+ }
4340
4415
  break;
4341
4416
  default:
4342
4417
  this.#d.log(`${name} \u547D\u4EE4\u884C\u6CA1\u767B\u5F55\uFF0C\u627E\u4E0D\u5230\u5B83\u7684\u547D\u4EE4\u884C\uFF0C\u6CA1\u6253\u5F00\u767B\u5F55\u7A97\u53E3`);
@@ -4344,14 +4419,15 @@ var LoginGuide = class {
4344
4419
  return r.action;
4345
4420
  }
4346
4421
  };
4347
- function defaultLoginGuide(log2, now = () => Date.now()) {
4422
+ function defaultLoginGuide(log2, now = () => Date.now(), onNeedStandalone) {
4348
4423
  if (process.env.VITEST) return { onNotLoggedIn: async () => "unsupported" };
4349
4424
  return new LoginGuide({
4350
4425
  now,
4351
4426
  command: (tool) => loginCommandFor(tool, defaultCommandDeps()),
4352
4427
  gui: () => guiAvailable(defaultGuiDeps()),
4353
4428
  open: (cmd) => openLoginWindow(cmd, defaultLoginOpenDeps()),
4354
- log: log2
4429
+ log: log2,
4430
+ onNeedStandalone
4355
4431
  });
4356
4432
  }
4357
4433
  async function autoEnableLoginLog(tools, check, log2) {
@@ -4368,6 +4444,487 @@ async function autoEnableLoginLog(tools, check, log2) {
4368
4444
  }
4369
4445
  }
4370
4446
 
4447
+ // src/daemon/workbuddySetup.ts
4448
+ import { execFile as execFile7, spawn as spawn4 } from "child_process";
4449
+ import { existsSync as existsSync17, mkdirSync as mkdirSync16, readFileSync as readFileSync17, rmSync as rmSync9, statSync as statSync6, writeFileSync as writeFileSync15 } from "fs";
4450
+ import { dirname as dirname14, join as join20 } from "path";
4451
+ var DIALOG_TITLE = "DeskBuddy";
4452
+ var BTN_INSTALL = "\u5B89\u88C5";
4453
+ var BTN_LATER = "\u6682\u4E0D";
4454
+ var DIALOG_TIMEOUT_S = 120;
4455
+ var DIALOG_PROC_SLACK_MS = 15 * 1e3;
4456
+ var NPM_TIMEOUT_MS = 5 * 60 * 1e3;
4457
+ var ASK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
4458
+ var NPM_INSTALL_ARGS = ["i", "-g", WORKBUDDY_STANDALONE_PKG];
4459
+ var DIALOG_TEXT = [
4460
+ "\u68C0\u6D4B\u5230\u8FD9\u53F0\u7535\u8111\u88C5\u4E86 WorkBuddy\uFF0C\u4F46\u6CA1\u6709\u5B83\u7684\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08CodeBuddy Code\uFF09\u3002",
4461
+ "",
4462
+ "\u88C5\u4E86\u72EC\u7ACB\u7248\uFF0C\u53EF\u4EE5\u5728\u8BBE\u5907\u4E0A\u76F4\u63A5\u7ED9 WorkBuddy \u65B0\u5EFA\u4EFB\u52A1\u3001\u56DE\u590D\u5DF2\u6709\u4EFB\u52A1\uFF1B\u4E0D\u88C5\u7684\u8BDD\uFF0C\u8BBE\u5907\u4E0A\u53EA\u80FD\u770B App \u91CC\u4EFB\u52A1\u7684\u72B6\u6001\u3001\u56DE\u7B54\u6388\u6743\u3002",
4463
+ "",
4464
+ `\u73B0\u5728\u5B89\u88C5\u5417\uFF1F\uFF08\u4F1A\u6267\u884C npm i -g ${WORKBUDDY_STANDALONE_PKG}\uFF0C\u88C5\u5B8C\u6253\u5F00\u4E00\u4E2A\u7EC8\u7AEF\u7A97\u53E3\uFF0C\u8F93\u5165 /login \u9009\u56FD\u5185\u7248\u767B\u5F55\uFF09`
4465
+ ].join("\n");
4466
+ var LIMITED_NOTE = "\u8BBE\u5907\u4E0A\u53EA\u80FD\u770B\u4EFB\u52A1\u72B6\u6001\u3001\u56DE\u7B54\u6388\u6743";
4467
+ var defaultRunner = (file, args, opts) => new Promise((resolve10) => {
4468
+ try {
4469
+ const child = execFile7(
4470
+ file,
4471
+ args,
4472
+ { env: opts.env, timeout: opts.timeoutMs, windowsHide: true, maxBuffer: 8 * 1024 * 1024, encoding: "utf8" },
4473
+ (err, stdout7, stderr) => {
4474
+ const e = err;
4475
+ resolve10({
4476
+ code: e ? typeof e.code === "number" ? e.code : null : 0,
4477
+ stdout: String(stdout7 ?? ""),
4478
+ stderr: String(stderr ?? ""),
4479
+ timedOut: !!e && (e.killed === true || !!e.signal) && typeof e.code !== "string",
4480
+ spawnError: !!e && typeof e.code === "string"
4481
+ });
4482
+ }
4483
+ );
4484
+ child.stdin?.end();
4485
+ } catch {
4486
+ resolve10({ code: null, stdout: "", stderr: "", timedOut: false, spawnError: true });
4487
+ }
4488
+ });
4489
+ function checkTimeout(s2) {
4490
+ if (!Number.isInteger(s2) || s2 < 1 || s2 > 3600) throw new Error("\u5F39\u6846\u8D85\u65F6\u4E0D\u5408\u6CD5");
4491
+ }
4492
+ function macDialogArgs(text, title, later, install, timeoutS = DIALOG_TIMEOUT_S) {
4493
+ checkTimeout(timeoutS);
4494
+ for (const a of [text, title, later, install]) if (a.includes("\0")) throw new Error("\u5F39\u6846\u6587\u5B57\u4E0D\u5408\u6CD5");
4495
+ return [
4496
+ "-e",
4497
+ "on run argv",
4498
+ "-e",
4499
+ `display dialog (item 1 of argv) with title (item 2 of argv) buttons {(item 3 of argv), (item 4 of argv)} giving up after ${timeoutS}`,
4500
+ "-e",
4501
+ "end run",
4502
+ text,
4503
+ title,
4504
+ later,
4505
+ install
4506
+ ];
4507
+ }
4508
+ function parseMacDialog(stdout7, later = BTN_LATER, install = BTN_INSTALL) {
4509
+ if (/gave up:\s*true/i.test(stdout7)) return "timeout";
4510
+ const m = /button returned:([^,\r\n]*)/.exec(stdout7);
4511
+ if (!m) return "error";
4512
+ const b = m[1].trim();
4513
+ if (b === install) return "install";
4514
+ if (b === later) return "later";
4515
+ return "error";
4516
+ }
4517
+ function psLiteral(s2) {
4518
+ if (/[\0\r\n]/.test(s2)) throw new Error("\u5F39\u6846\u6587\u5B57\u4E0D\u5408\u6CD5");
4519
+ return `'${s2.replace(/['‘-‛]/g, (c) => c + c)}'`;
4520
+ }
4521
+ function psMultiline(s2) {
4522
+ return s2.split("\n").map((l) => psLiteral(l)).join(" + [char]10 + ");
4523
+ }
4524
+ function winDialogScript(text, title, later, install, timeoutS = DIALOG_TIMEOUT_S) {
4525
+ checkTimeout(timeoutS);
4526
+ return [
4527
+ "$ErrorActionPreference = 'Stop'",
4528
+ "[Console]::OutputEncoding = [Text.Encoding]::UTF8",
4529
+ "Add-Type -AssemblyName System.Windows.Forms",
4530
+ "Add-Type -AssemblyName System.Drawing",
4531
+ "[System.Windows.Forms.Application]::EnableVisualStyles()",
4532
+ "$f = New-Object System.Windows.Forms.Form",
4533
+ `$f.Text = ${psLiteral(title)}`,
4534
+ "$f.TopMost = $true",
4535
+ "$f.StartPosition = 'CenterScreen'",
4536
+ "$f.FormBorderStyle = 'FixedDialog'",
4537
+ "$f.MaximizeBox = $false",
4538
+ "$f.MinimizeBox = $false",
4539
+ "$f.ClientSize = New-Object System.Drawing.Size(480, 230)",
4540
+ "$l = New-Object System.Windows.Forms.Label",
4541
+ `$l.Text = ${psMultiline(text)}`,
4542
+ "$l.SetBounds(16, 14, 448, 160)",
4543
+ "$f.Controls.Add($l)",
4544
+ "$no = New-Object System.Windows.Forms.Button",
4545
+ `$no.Text = ${psLiteral(later)}`,
4546
+ "$no.SetBounds(270, 186, 90, 30)",
4547
+ "$no.DialogResult = 'No'",
4548
+ "$yes = New-Object System.Windows.Forms.Button",
4549
+ `$yes.Text = ${psLiteral(install)}`,
4550
+ "$yes.SetBounds(374, 186, 90, 30)",
4551
+ "$yes.DialogResult = 'Yes'",
4552
+ "$f.Controls.Add($no)",
4553
+ "$f.Controls.Add($yes)",
4554
+ "$t = New-Object System.Windows.Forms.Timer",
4555
+ `$t.Interval = ${timeoutS * 1e3}`,
4556
+ "$t.Add_Tick({ $t.Stop(); $f.Tag = 'timeout'; $f.Close() })",
4557
+ "$t.Start()",
4558
+ "$r = $f.ShowDialog()",
4559
+ "$t.Stop()",
4560
+ "if ($f.Tag -eq 'timeout') { 'timeout' } elseif ($r -eq 'Yes') { 'install' } elseif ($r -eq 'No') { 'later' } else { 'timeout' }"
4561
+ ].join("\r\n");
4562
+ }
4563
+ function winDialogArgs(script) {
4564
+ return ["-NoProfile", "-NonInteractive", "-Sta", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encodePs(script)];
4565
+ }
4566
+ function parseWinDialog(stdout7) {
4567
+ const last = stdout7.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).pop();
4568
+ return last === "install" || last === "later" || last === "timeout" ? last : "error";
4569
+ }
4570
+ async function askDialog(platform, run2, timeoutS = DIALOG_TIMEOUT_S) {
4571
+ const timeoutMs = timeoutS * 1e3 + DIALOG_PROC_SLACK_MS;
4572
+ if (platform === "darwin") {
4573
+ const r = await run2("osascript", macDialogArgs(DIALOG_TEXT, DIALOG_TITLE, BTN_LATER, BTN_INSTALL, timeoutS), { timeoutMs });
4574
+ if (r.timedOut) return "timeout";
4575
+ return parseMacDialog(r.stdout);
4576
+ }
4577
+ if (platform === "win32") {
4578
+ const r = await run2("powershell.exe", winDialogArgs(winDialogScript(DIALOG_TEXT, DIALOG_TITLE, BTN_LATER, BTN_INSTALL, timeoutS)), { timeoutMs });
4579
+ if (r.timedOut) return "timeout";
4580
+ return parseWinDialog(r.stdout);
4581
+ }
4582
+ return "error";
4583
+ }
4584
+ function npmPlan(d) {
4585
+ if ((d.platform ?? process.platform) === "win32") {
4586
+ for (const f of whichAllIn("npm", d)) {
4587
+ if (!/\.cmd$/i.test(f)) continue;
4588
+ const dir = dirname14(f);
4589
+ const cli = join20(dir, "node_modules", "npm", "bin", "npm-cli.js");
4590
+ if (!d.exists(cli)) continue;
4591
+ const node = join20(dir, "node.exe");
4592
+ return { file: d.exists(node) ? node : d.nodePath, args: [cli, ...NPM_INSTALL_ARGS] };
4593
+ }
4594
+ return null;
4595
+ }
4596
+ const npm = whichIn("npm", d);
4597
+ return npm ? { file: npm, args: [...NPM_INSTALL_ARGS] } : null;
4598
+ }
4599
+ function manualLoginCommand(platform) {
4600
+ return platform === "win32" ? '$env:CODEBUDDY_CONFIG_DIR="$HOME\\.workbuddy"; codebuddy' : "CODEBUDDY_CONFIG_DIR=~/.workbuddy codebuddy";
4601
+ }
4602
+ function manualHint(platform) {
4603
+ return `\u8BF7\u5728\u7535\u8111\u7EC8\u7AEF\u8FD0\u884C npm i -g ${WORKBUDDY_STANDALONE_PKG}\uFF0C\u88C5\u597D\u540E\u8FD0\u884C ${manualLoginCommand(platform)}\uFF0C\u8F93\u5165 /login \u9009\u56FD\u5185\u7248\u767B\u5F55`;
4604
+ }
4605
+ function tailOf(s2) {
4606
+ const t = s2.replace(/\s+/g, " ").trim();
4607
+ return t.length > 200 ? "\u2026" + t.slice(-200) : t;
4608
+ }
4609
+ async function installStandalone(d) {
4610
+ const plan = d.plan();
4611
+ if (!plan) return { ok: false, reason: "\u627E\u4E0D\u5230 npm\uFF08\u6CA1\u88C5 Node.js\uFF0C\u6216\u88C5\u6CD5\u8BA4\u4E0D\u51FA\uFF09" };
4612
+ const r = await d.run(plan.file, plan.args, { env: d.env(), timeoutMs: NPM_TIMEOUT_MS });
4613
+ if (r.spawnError) return { ok: false, reason: "npm \u8D77\u4E0D\u6765" };
4614
+ if (r.timedOut) return { ok: false, reason: `npm \u5B89\u88C5\u8D85\u8FC7 ${Math.round(NPM_TIMEOUT_MS / 6e4)} \u5206\u949F\u6CA1\u5B8C\u6210` };
4615
+ if (r.code !== 0) {
4616
+ const text = `${r.stderr}
4617
+ ${r.stdout}`;
4618
+ if (/\bEACCES\b|permission denied/i.test(text)) {
4619
+ return { ok: false, reason: "npm \u5168\u5C40\u76EE\u5F55\u6CA1\u6709\u5199\u6743\u9650\uFF08\u6309 npm \u6587\u6863\u6539 prefix\uFF0C\u6216\u5728\u7EC8\u7AEF\u91CC\u7528 sudo \u88C5\uFF09" };
4620
+ }
4621
+ return { ok: false, reason: `npm \u9000\u51FA\u7801 ${r.code ?? "\u65E0"}${tailOf(r.stderr) ? `\uFF1A${tailOf(r.stderr)}` : ""}` };
4622
+ }
4623
+ if (!d.located()) return { ok: false, reason: "npm \u8BF4\u88C5\u597D\u4E86\uFF0C\u4F46\u5728\u5E38\u89C1\u76EE\u5F55\u91CC\u627E\u4E0D\u5230 codebuddy\uFF08npm \u5168\u5C40\u76EE\u5F55\u4E0D\u5728 PATH \u4E0A\uFF09" };
4624
+ return { ok: true };
4625
+ }
4626
+ var isDaemonSource = (s2) => s2 === "daemon" || s2 === "runtime";
4627
+ async function runOffer(source, d, opts = {}) {
4628
+ if (isDaemonSource(source)) {
4629
+ const t = d.tool();
4630
+ if (!t.enabled || t.disabled) return "no_app";
4631
+ }
4632
+ const cfg = d.setup();
4633
+ if (source !== "manual" && cfg.declined) return "declined";
4634
+ if (isDaemonSource(source) && cfg.askedAt !== void 0) {
4635
+ const since = d.now() - cfg.askedAt;
4636
+ if (since >= 0 && since < (d.throttleMs ?? ASK_THROTTLE_MS)) return "throttled";
4637
+ }
4638
+ const st = d.state();
4639
+ if (st.standalone) return "has_standalone";
4640
+ if (!st.app && source !== "manual") return "no_app";
4641
+ if (!opts.skipAsk) {
4642
+ let gui = false;
4643
+ try {
4644
+ gui = d.gui();
4645
+ } catch {
4646
+ gui = false;
4647
+ }
4648
+ if (!gui) {
4649
+ d.saveSetup({ askedAt: d.now() });
4650
+ d.log(`WorkBuddy \u6CA1\u6709\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08${LIMITED_NOTE}\uFF09\uFF0C\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\u3001\u4E0D\u5F39\u6846\uFF1A${manualHint(d.platform)}`);
4651
+ return "no_gui";
4652
+ }
4653
+ }
4654
+ const release2 = d.lock();
4655
+ if (!release2) return "busy";
4656
+ try {
4657
+ if (!opts.skipAsk) {
4658
+ d.saveSetup({ askedAt: d.now() });
4659
+ let ans;
4660
+ try {
4661
+ ans = await d.ask();
4662
+ } catch {
4663
+ ans = "error";
4664
+ }
4665
+ if (ans === "later") {
4666
+ d.saveSetup({ declined: true });
4667
+ d.log("\u7528\u6237\u9009\u4E86\u300C\u6682\u4E0D\u300D\u5B89\u88C5 WorkBuddy \u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF0C\u4EE5\u540E\u4E0D\u518D\u95EE\uFF08deskbuddy workbuddy-setup \u53EF\u518D\u88C5\uFF09");
4668
+ return "later";
4669
+ }
4670
+ if (ans !== "install") {
4671
+ d.log(`WorkBuddy \u72EC\u7ACB\u7248\u8BE2\u95EE${ans === "timeout" ? "\u8D85\u65F6\u6CA1\u4EBA\u70B9" : "\u6CA1\u5F39\u51FA\u6765"}\uFF0C\u6309\u300C\u6682\u4E0D\u300D\u5904\u7406\u4F46\u4E0D\u8BB0\u4F4F\uFF0C\u4E4B\u540E\u8FD8\u4F1A\u518D\u95EE`);
4672
+ return "timeout";
4673
+ }
4674
+ }
4675
+ d.log(`\u5F00\u59CB\u5B89\u88C5 WorkBuddy \u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08npm i -g ${WORKBUDDY_STANDALONE_PKG}\uFF09`);
4676
+ let r;
4677
+ try {
4678
+ r = await d.install();
4679
+ } catch (e) {
4680
+ r = { ok: false, reason: e?.message ?? String(e) };
4681
+ }
4682
+ if (!r.ok) {
4683
+ d.log(`WorkBuddy \u72EC\u7ACB\u7248\u547D\u4EE4\u884C\u6CA1\u88C5\u4E0A\uFF1A${r.reason}\u3002${manualHint(d.platform)}`);
4684
+ return "install_failed";
4685
+ }
4686
+ d.saveSetup({ declined: null });
4687
+ d.log("WorkBuddy \u72EC\u7ACB\u7248\u547D\u4EE4\u884C\u5DF2\u88C5\u597D");
4688
+ try {
4689
+ d.log(await d.openLogin());
4690
+ } catch (e) {
4691
+ d.log(`\u6CA1\u80FD\u6253\u5F00\u767B\u5F55\u7A97\u53E3\uFF08${e?.message ?? e}\uFF09\uFF1A\u8BF7\u5728\u7535\u8111\u7EC8\u7AEF\u8FD0\u884C ${manualLoginCommand(d.platform)}\uFF0C\u8F93\u5165 /login \u9009\u56FD\u5185\u7248`);
4692
+ }
4693
+ return "installed";
4694
+ } finally {
4695
+ try {
4696
+ release2();
4697
+ } catch {
4698
+ }
4699
+ }
4700
+ }
4701
+ var LOCK_TTL_MS = 15 * 60 * 1e3;
4702
+ function lockPath() {
4703
+ return join20(deskbuddyHome(), "workbuddy-setup.lock");
4704
+ }
4705
+ function readLock(path) {
4706
+ try {
4707
+ const o = JSON.parse(readFileSync17(path, "utf8"));
4708
+ return typeof o.pid === "number" && typeof o.at === "number" ? { pid: o.pid, at: o.at } : null;
4709
+ } catch {
4710
+ return null;
4711
+ }
4712
+ }
4713
+ function lockHeld(d) {
4714
+ if (!existsSync17(d.path)) return false;
4715
+ const cur = readLock(d.path);
4716
+ if (!cur) return true;
4717
+ if (d.now() - cur.at > LOCK_TTL_MS || cur.at - d.now() > LOCK_TTL_MS) return false;
4718
+ return d.alive ? d.alive(cur.pid) : true;
4719
+ }
4720
+ function acquireLock(d) {
4721
+ for (let i = 0; i < 2; i++) {
4722
+ const mine = { pid: d.pid, at: d.now() };
4723
+ try {
4724
+ mkdirSync16(dirname14(d.path), { recursive: true });
4725
+ writeFileSync15(d.path, JSON.stringify(mine), { flag: "wx", mode: 384 });
4726
+ return () => {
4727
+ const cur = readLock(d.path);
4728
+ if (cur && cur.pid === mine.pid && cur.at === mine.at) rmSync9(d.path, { force: true });
4729
+ };
4730
+ } catch (e) {
4731
+ if (e?.code !== "EEXIST") return null;
4732
+ if (i > 0) return null;
4733
+ let stale = !lockHeld(d);
4734
+ if (!stale && !readLock(d.path)) {
4735
+ try {
4736
+ stale = d.now() - statSync6(d.path).mtimeMs > LOCK_TTL_MS;
4737
+ } catch {
4738
+ stale = true;
4739
+ }
4740
+ }
4741
+ if (!stale) return null;
4742
+ try {
4743
+ rmSync9(d.path, { force: true });
4744
+ } catch {
4745
+ return null;
4746
+ }
4747
+ }
4748
+ }
4749
+ return null;
4750
+ }
4751
+ function pidAlive2(pid) {
4752
+ try {
4753
+ process.kill(pid, 0);
4754
+ return true;
4755
+ } catch (e) {
4756
+ return e?.code === "EPERM";
4757
+ }
4758
+ }
4759
+ var defaultLockDeps = () => ({ path: lockPath(), pid: process.pid, now: () => Date.now(), alive: pidAlive2 });
4760
+ var WorkbuddyOffer = class {
4761
+ #d;
4762
+ #busy = false;
4763
+ constructor(d) {
4764
+ this.#d = d;
4765
+ }
4766
+ async maybeAsk(source) {
4767
+ if (this.#busy) return "busy";
4768
+ this.#busy = true;
4769
+ try {
4770
+ return await runOffer(source, this.#d);
4771
+ } catch (e) {
4772
+ this.#d.log(`WorkBuddy \u72EC\u7ACB\u7248\u8BE2\u95EE\u51FA\u9519\uFF1A${e?.message ?? e}`);
4773
+ return "timeout";
4774
+ } finally {
4775
+ this.#busy = false;
4776
+ }
4777
+ }
4778
+ };
4779
+ function workbuddyState(d = defaultLocateDeps()) {
4780
+ if (locateWorkbuddyStandalone(d) != null) return { standalone: true, app: false };
4781
+ return { standalone: false, app: locateWorkbuddyApp(d) != null };
4782
+ }
4783
+ function daemonLocateDeps() {
4784
+ return { ...defaultLocateDeps(), winAppHints: void 0 };
4785
+ }
4786
+ function readTool() {
4787
+ const cfg = loadConfig();
4788
+ return { enabled: cfg.enabledTools.includes("workbuddy"), disabled: (cfg.disabledTools ?? []).includes("workbuddy") };
4789
+ }
4790
+ function readSetup() {
4791
+ return { ...loadConfig().workbuddySetup ?? {} };
4792
+ }
4793
+ function writeSetup(patch) {
4794
+ const cfg = loadConfig();
4795
+ const next = { ...cfg.workbuddySetup ?? {} };
4796
+ if (patch.declined === null) delete next.declined;
4797
+ else if (patch.declined !== void 0) next.declined = patch.declined;
4798
+ if (patch.askedAt !== void 0) next.askedAt = patch.askedAt;
4799
+ cfg.workbuddySetup = Object.keys(next).length > 0 ? next : void 0;
4800
+ saveConfig(cfg);
4801
+ }
4802
+ async function defaultOpenLogin(platform = process.platform) {
4803
+ const cmd = loginCommandFor("workbuddy", defaultCommandDeps());
4804
+ if ("error" in cmd) return `\u72EC\u7ACB\u7248\u88C5\u597D\u4E86\uFF0C\u4F46\u6CA1\u627E\u5230\u80FD\u5F00\u767B\u5F55\u7A97\u53E3\u7684\u547D\u4EE4\u884C\uFF1A\u8BF7\u5728\u7535\u8111\u7EC8\u7AEF\u8FD0\u884C ${manualLoginCommand(platform)}\uFF0C\u8F93\u5165 /login \u9009\u56FD\u5185\u7248`;
4805
+ if (!guiAvailable(defaultGuiDeps())) return `\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\uFF0C\u6CA1\u6253\u5F00\u767B\u5F55\u7A97\u53E3\uFF1A\u8BF7\u5728\u7535\u8111\u7EC8\u7AEF\u8FD0\u884C ${manualCommand(cmd, platform)}\uFF0C\u8F93\u5165 /login \u9009\u56FD\u5185\u7248`;
4806
+ await openLoginWindow(cmd, defaultLoginOpenDeps());
4807
+ return `\u5DF2\u6253\u5F00 WorkBuddy \u767B\u5F55\u7A97\u53E3${WORKBUDDY_LOGIN_HINT}`;
4808
+ }
4809
+ function defaultOfferDeps(log2) {
4810
+ const platform = process.platform;
4811
+ return {
4812
+ state: () => workbuddyState(daemonLocateDeps()),
4813
+ tool: readTool,
4814
+ setup: readSetup,
4815
+ saveSetup: writeSetup,
4816
+ lock: () => acquireLock(defaultLockDeps()),
4817
+ gui: () => guiAvailable(defaultGuiDeps()),
4818
+ ask: () => askDialog(platform, defaultRunner),
4819
+ install: () => installStandalone({
4820
+ plan: () => npmPlan(defaultLocateDeps()),
4821
+ run: defaultRunner,
4822
+ env: () => mergeEnv(process.env, { PATH: childPath() }, platform),
4823
+ located: () => locateWorkbuddyStandalone() != null
4824
+ }),
4825
+ openLogin: () => defaultOpenLogin(platform),
4826
+ log: (l) => log2(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`),
4827
+ now: () => Date.now(),
4828
+ platform
4829
+ };
4830
+ }
4831
+ function offerDisabled(env) {
4832
+ return !!env.VITEST || env.DESKBUDDY_SKIP_WORKBUDDY_SETUP === "1";
4833
+ }
4834
+ var offerSingleton = null;
4835
+ function defaultWorkbuddyOffer(log2, env = process.env) {
4836
+ if (offerDisabled(env)) return null;
4837
+ if (!offerSingleton) offerSingleton = new WorkbuddyOffer(defaultOfferDeps(log2));
4838
+ return offerSingleton;
4839
+ }
4840
+ var PROMPT_DEDUP_MS = DIALOG_TIMEOUT_S * 1e3 + 60 * 1e3;
4841
+ function startStandalonePrompt(source, out, d) {
4842
+ let st;
4843
+ try {
4844
+ st = d.state();
4845
+ } catch {
4846
+ return "none";
4847
+ }
4848
+ if (st.standalone || !st.app) return "none";
4849
+ out("");
4850
+ if (d.setup().declined) {
4851
+ out(`\xB7 WorkBuddy\uFF1A\u53EA\u88C5\u4E86 App\uFF0C\u4E4B\u524D\u9009\u8FC7\u6682\u4E0D\u5B89\u88C5\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08${LIMITED_NOTE}\uFF09\uFF1B\u9700\u8981\u65F6\u8FD0\u884C deskbuddy workbuddy-setup`);
4852
+ return "declined";
4853
+ }
4854
+ const askedAt = d.setup().askedAt;
4855
+ const recent = askedAt !== void 0 && d.now() - askedAt >= 0 && d.now() - askedAt < PROMPT_DEDUP_MS;
4856
+ let held = false;
4857
+ try {
4858
+ held = d.locked();
4859
+ } catch {
4860
+ held = false;
4861
+ }
4862
+ if (held || recent) {
4863
+ out(`WorkBuddy\uFF1A\u53EA\u88C5\u4E86 App\uFF0C\u6CA1\u6709\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF1B\u7535\u8111\u4E0A\u5DF2\u7ECF\u5728\u95EE\u8981\u4E0D\u8981\u5B89\u88C5\uFF08\u4E0D\u88C5${LIMITED_NOTE}\uFF09`);
4864
+ return "pending";
4865
+ }
4866
+ let gui = false;
4867
+ try {
4868
+ gui = d.gui();
4869
+ } catch {
4870
+ gui = false;
4871
+ }
4872
+ if (!gui) {
4873
+ out(`\u26A0\uFE0F WorkBuddy\uFF1A\u53EA\u88C5\u4E86 App\uFF0C\u6CA1\u6709\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08${LIMITED_NOTE}\uFF09\u3002\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\uFF0C\u6CA1\u5F39\u8BE2\u95EE\u6846\uFF1A${manualHint(d.platform)}`);
4874
+ return "no_gui";
4875
+ }
4876
+ try {
4877
+ d.saveSetup({ askedAt: d.now() });
4878
+ d.spawnChild(source);
4879
+ } catch (e) {
4880
+ out(`\u26A0\uFE0F WorkBuddy\uFF1A\u53EA\u88C5\u4E86 App\uFF0C\u6CA1\u6709\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08${LIMITED_NOTE}\uFF09\u3002\u6CA1\u80FD\u5F39\u51FA\u8BE2\u95EE\u6846\uFF08${e?.message ?? e}\uFF09\uFF1A${manualHint(d.platform)}`);
4881
+ return "spawn_failed";
4882
+ }
4883
+ out(
4884
+ `WorkBuddy\uFF1A\u53EA\u88C5\u4E86 App\uFF0C\u6CA1\u6709\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\u3002\u5DF2\u5728\u7535\u8111\u4E0A\u5F39\u51FA\u8BE2\u95EE\u6846\uFF08${Math.round(DIALOG_TIMEOUT_S / 60)} \u5206\u949F\u5185\u9009\u62E9\uFF09\uFF1A\u88C5\u4E86\u53EF\u4EE5\u5728\u8BBE\u5907\u4E0A\u76F4\u63A5\u65B0\u5EFA / \u56DE\u590D WorkBuddy \u4EFB\u52A1\uFF0C\u4E0D\u88C5${LIMITED_NOTE}`
4885
+ );
4886
+ return "asked";
4887
+ }
4888
+ function promptSummaryLine(r, platform = process.platform) {
4889
+ switch (r) {
4890
+ case "asked":
4891
+ case "pending":
4892
+ return "WorkBuddy \u72EC\u7ACB\u7248\u547D\u4EE4\u884C: \u672A\u5B89\u88C5\uFF0C\u5DF2\u5728\u7535\u8111\u4E0A\u5F39\u7A97\u8BE2\u95EE\u662F\u5426\u5B89\u88C5\uFF08\u88C5\u4E86\u624D\u80FD\u5728\u8BBE\u5907\u4E0A\u65B0\u5EFA / \u56DE\u590D WorkBuddy \u4EFB\u52A1\uFF09";
4893
+ case "declined":
4894
+ return `WorkBuddy \u72EC\u7ACB\u7248\u547D\u4EE4\u884C: \u672A\u5B89\u88C5\uFF08\u4E4B\u524D\u9009\u8FC7\u6682\u4E0D\uFF0C${LIMITED_NOTE}\uFF1B\u8FD0\u884C deskbuddy workbuddy-setup \u53EF\u518D\u88C5\uFF09`;
4895
+ case "no_gui":
4896
+ case "spawn_failed":
4897
+ return `WorkBuddy \u72EC\u7ACB\u7248\u547D\u4EE4\u884C: \u672A\u5B89\u88C5\uFF08${LIMITED_NOTE}\uFF09\u3002${manualHint(platform)}`;
4898
+ default:
4899
+ return "";
4900
+ }
4901
+ }
4902
+ function defaultPromptStartDeps(env = process.env) {
4903
+ if (offerDisabled(env)) return null;
4904
+ return {
4905
+ state: () => workbuddyState(),
4906
+ setup: readSetup,
4907
+ saveSetup: writeSetup,
4908
+ locked: () => lockHeld(defaultLockDeps()),
4909
+ gui: () => guiAvailable(defaultGuiDeps()),
4910
+ spawnChild: (source) => {
4911
+ const cli = process.argv[1];
4912
+ if (!cli || !existsSync17(cli)) throw new Error("\u627E\u4E0D\u5230 deskbuddy \u81EA\u5DF1\u7684\u5165\u53E3\u6587\u4EF6");
4913
+ const c = spawn4(process.execPath, [cli, "workbuddy-setup", "--from", source], {
4914
+ detached: true,
4915
+ stdio: "ignore",
4916
+ windowsHide: true,
4917
+ env: process.env
4918
+ });
4919
+ c.on("error", () => {
4920
+ });
4921
+ c.unref();
4922
+ },
4923
+ now: () => Date.now(),
4924
+ platform: process.platform
4925
+ };
4926
+ }
4927
+
4371
4928
  // src/commands/enable.ts
4372
4929
  function stdout(line) {
4373
4930
  process.stdout.write(line + "\n");
@@ -4432,14 +4989,14 @@ function installTools(tools, out, shim = defaultShimEnvForInstall(), opts = {})
4432
4989
  installed.push(tool);
4433
4990
  out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
4434
4991
  try {
4435
- if (adapter.installWindowGate?.(configPath2, join20(dirname13(hookPath), "gate.cjs"), tool)) {
4992
+ if (adapter.installWindowGate?.(configPath2, join21(dirname15(hookPath), "gate.cjs"), tool)) {
4436
4993
  out(` \u7A97\u53E3\u91CC\u5F39\u6388\u6743\u6846\u65F6\uFF0C\u8BBE\u5907\u4E0A\u4E5F\u80FD\u786E\u8BA4\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
4437
4994
  }
4438
4995
  } catch (e) {
4439
4996
  out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
4440
4997
  }
4441
4998
  try {
4442
- if (adapter.installRewake?.(configPath2, join20(dirname13(hookPath), "wake.cjs"), tool)) {
4999
+ if (adapter.installRewake?.(configPath2, join21(dirname15(hookPath), "wake.cjs"), tool)) {
4443
5000
  out(` \u7A97\u53E3\u5F00\u7740\u65F6\uFF0C\u8BBE\u5907\u6307\u4EE4\u76F4\u63A5\u5728\u8FD9\u4E2A\u7A97\u53E3\u91CC\u6267\u884C\uFF08\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u751F\u6548\uFF09`);
4444
5001
  }
4445
5002
  } catch (e) {
@@ -4469,6 +5026,14 @@ async function runEnable(tools, opts = {}) {
4469
5026
  ensureClientId();
4470
5027
  if (!opts.skipRefresh) await refreshRegistry();
4471
5028
  const { failed } = installTools(tools, out, opts.shim === void 0 ? defaultShimEnvForInstall() : opts.shim);
5029
+ if (!opts.skipLoginCheck && loadConfig().enabledTools.includes("workbuddy")) {
5030
+ const wb = opts.workbuddyPrompt === void 0 ? defaultPromptStartDeps() : opts.workbuddyPrompt;
5031
+ try {
5032
+ if (wb) startStandalonePrompt("enable", out, wb);
5033
+ } catch (e) {
5034
+ out(`\xB7 WorkBuddy \u72EC\u7ACB\u7248\u68C0\u67E5\u51FA\u9519\uFF1A${e?.message ?? e}\uFF08\u4E0D\u5F71\u54CD\u5B89\u88C5\uFF09`);
5035
+ }
5036
+ }
4472
5037
  if (!opts.skipAutostart) {
4473
5038
  out("");
4474
5039
  const target = autostartTarget(out);
@@ -4547,15 +5112,29 @@ var AutoEnabler = class {
4547
5112
  }
4548
5113
  /** 跑一轮。返回这轮新接入的工具(测试用) */
4549
5114
  tick() {
5115
+ let bound = false;
5116
+ try {
5117
+ return this.#tick((b) => bound = b);
5118
+ } finally {
5119
+ if (bound) {
5120
+ try {
5121
+ this.#d.onTick?.();
5122
+ } catch {
5123
+ }
5124
+ }
5125
+ }
5126
+ }
5127
+ #tick(setBound) {
4550
5128
  try {
4551
5129
  const cfg = this.#d.config();
4552
5130
  if (!cfg.boundAt) return [];
5131
+ setBound(true);
4553
5132
  const ok = this.#d.installable ?? (() => true);
4554
5133
  const candidates = autoEnableCandidates(this.#d.detect(), cfg).filter((t) => ok(t));
4555
5134
  for (const t of [...this.#failed.keys()]) if (!candidates.includes(t)) this.#failed.delete(t);
4556
5135
  if (candidates.length === 0) return [];
4557
- const lines2 = [];
4558
- const r = this.#d.install(candidates, (l) => lines2.push(l));
5136
+ const lines3 = [];
5137
+ const r = this.#d.install(candidates, (l) => lines3.push(l));
4559
5138
  this.#lastError = "";
4560
5139
  if (r.installed.length > 0) {
4561
5140
  this.#d.log(`[\u81EA\u52A8\u63A5\u5165] \u53D1\u73B0\u65B0\u88C5\u7684\u667A\u80FD\u4F53\u5E76\u5DF2\u63A5\u5165\uFF1A${r.installed.join(", ")}`);
@@ -4582,13 +5161,13 @@ var AutoEnabler = class {
4582
5161
 
4583
5162
  // src/daemon/index.ts
4584
5163
  import { randomBytes as randomBytes7 } from "crypto";
4585
- import { existsSync as existsSync20, statSync as statSync14 } from "fs";
4586
- import { basename as basename9, dirname as dirname19, join as join37 } from "path";
5164
+ import { existsSync as existsSync25, statSync as statSync17 } from "fs";
5165
+ import { basename as basename12, dirname as dirname24, join as join41 } from "path";
4587
5166
  import WebSocket2 from "ws";
4588
5167
 
4589
5168
  // src/core/state.ts
4590
- import { existsSync as existsSync16, mkdirSync as mkdirSync17, readdirSync as readdirSync6, readFileSync as readFileSync18, renameSync as renameSync11, rmSync as rmSync10, writeFileSync as writeFileSync16 } from "fs";
4591
- import { join as join22 } from "path";
5169
+ import { existsSync as existsSync18, mkdirSync as mkdirSync18, readdirSync as readdirSync6, readFileSync as readFileSync19, renameSync as renameSync11, rmSync as rmSync11, writeFileSync as writeFileSync17 } from "fs";
5170
+ import { join as join23 } from "path";
4592
5171
 
4593
5172
  // src/core/process.ts
4594
5173
  import { execFileSync as execFileSync11 } from "child_process";
@@ -4697,29 +5276,29 @@ function programOf(args) {
4697
5276
  }
4698
5277
 
4699
5278
  // src/core/ended.ts
4700
- import { closeSync as closeSync4, mkdirSync as mkdirSync16, openSync as openSync4, readdirSync as readdirSync5, readFileSync as readFileSync17, readSync as readSync3, realpathSync as realpathSync2, renameSync as renameSync10, rmSync as rmSync9, statSync as statSync6, writeFileSync as writeFileSync15 } from "fs";
4701
- import { join as join21 } from "path";
5279
+ import { closeSync as closeSync4, mkdirSync as mkdirSync17, openSync as openSync4, readdirSync as readdirSync5, readFileSync as readFileSync18, readSync as readSync3, realpathSync as realpathSync2, renameSync as renameSync10, rmSync as rmSync10, statSync as statSync7, writeFileSync as writeFileSync16 } from "fs";
5280
+ import { join as join22 } from "path";
4702
5281
  var ENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
4703
5282
  var SID_RE = /^[A-Za-z0-9_-]{1,64}$/;
4704
5283
  function endedDir(home = deskbuddyHome()) {
4705
- return join21(home, "ended");
5284
+ return join22(home, "ended");
4706
5285
  }
4707
5286
  function writeEnded(s2, now = Date.now()) {
4708
5287
  if (!SID_RE.test(s2.sessionId)) return;
4709
5288
  try {
4710
5289
  const dir = endedDir();
4711
- mkdirSync16(dir, { recursive: true, mode: 448 });
5290
+ mkdirSync17(dir, { recursive: true, mode: 448 });
4712
5291
  const rec = { sessionId: s2.sessionId, tool: s2.tool, cwd: s2.cwd ?? "", interactive: s2.interactive === true, endedAt: now };
4713
5292
  if (Number.isInteger(s2.turns) && s2.turns >= 0) rec.turns = s2.turns;
4714
5293
  if (typeof s2.firstPrompt === "string" && s2.firstPrompt) rec.firstPrompt = s2.firstPrompt;
4715
- const file = join21(dir, `${s2.sessionId}.json`);
5294
+ const file = join22(dir, `${s2.sessionId}.json`);
4716
5295
  const tmp = `${file}.${process.pid}.tmp`;
4717
- writeFileSync15(tmp, JSON.stringify(rec), { encoding: "utf8", mode: 384 });
5296
+ writeFileSync16(tmp, JSON.stringify(rec), { encoding: "utf8", mode: 384 });
4718
5297
  renameSync10(tmp, file);
4719
5298
  for (const n of readdirSync5(dir)) {
4720
- const f = join21(dir, n);
5299
+ const f = join22(dir, n);
4721
5300
  try {
4722
- if (now - statSync6(f).mtimeMs > ENDED_TTL_MS) rmSync9(f, { force: true });
5301
+ if (now - statSync7(f).mtimeMs > ENDED_TTL_MS) rmSync10(f, { force: true });
4723
5302
  } catch {
4724
5303
  }
4725
5304
  }
@@ -4729,7 +5308,7 @@ function writeEnded(s2, now = Date.now()) {
4729
5308
  function readEnded(sessionId, now = Date.now()) {
4730
5309
  if (!SID_RE.test(sessionId)) return null;
4731
5310
  try {
4732
- const r = JSON.parse(readFileSync17(join21(endedDir(), `${sessionId}.json`), "utf8"));
5311
+ const r = JSON.parse(readFileSync18(join22(endedDir(), `${sessionId}.json`), "utf8"));
4733
5312
  if (!r || r.sessionId !== sessionId || typeof r.endedAt !== "number" || now - r.endedAt > ENDED_TTL_MS) return null;
4734
5313
  return r;
4735
5314
  } catch {
@@ -4744,7 +5323,7 @@ function projectDirName(cwd) {
4744
5323
  function readChunk(file, fromEnd, max) {
4745
5324
  let fd;
4746
5325
  try {
4747
- const size = statSync6(file).size;
5326
+ const size = statSync7(file).size;
4748
5327
  const len = Math.min(size, max);
4749
5328
  const buf = Buffer.alloc(len);
4750
5329
  fd = openSync4(file, "r");
@@ -4770,14 +5349,14 @@ function claudeTranscriptCwd(sessionId, home = claudeHome()) {
4770
5349
  if (!SID_RE.test(sessionId)) return "";
4771
5350
  let dirs = [];
4772
5351
  try {
4773
- dirs = readdirSync5(join21(home, "projects"));
5352
+ dirs = readdirSync5(join22(home, "projects"));
4774
5353
  } catch {
4775
5354
  return "";
4776
5355
  }
4777
5356
  for (const d of dirs) {
4778
- const file = join21(home, "projects", d, `${sessionId}.jsonl`);
5357
+ const file = join22(home, "projects", d, `${sessionId}.jsonl`);
4779
5358
  try {
4780
- statSync6(file);
5359
+ statSync7(file);
4781
5360
  } catch {
4782
5361
  continue;
4783
5362
  }
@@ -4813,7 +5392,7 @@ function resolveEndedCwd(sessionId, tool, d) {
4813
5392
  }
4814
5393
  function isDirSync(p) {
4815
5394
  try {
4816
- return statSync6(p).isDirectory();
5395
+ return statSync7(p).isDirectory();
4817
5396
  } catch {
4818
5397
  return false;
4819
5398
  }
@@ -4821,23 +5400,23 @@ function isDirSync(p) {
4821
5400
 
4822
5401
  // src/core/state.ts
4823
5402
  function stateDir() {
4824
- return join22(deskbuddyHome(), "state");
5403
+ return join23(deskbuddyHome(), "state");
4825
5404
  }
4826
5405
  function statePath(sessionId) {
4827
5406
  const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
4828
- return join22(stateDir(), `${safe || "unknown"}.json`);
5407
+ return join23(stateDir(), `${safe || "unknown"}.json`);
4829
5408
  }
4830
5409
  function readState(sessionId) {
4831
5410
  try {
4832
- return JSON.parse(readFileSync18(statePath(sessionId), "utf8"));
5411
+ return JSON.parse(readFileSync19(statePath(sessionId), "utf8"));
4833
5412
  } catch {
4834
5413
  return null;
4835
5414
  }
4836
5415
  }
4837
5416
  function writeState(s2) {
4838
- mkdirSync17(stateDir(), { recursive: true });
5417
+ mkdirSync18(stateDir(), { recursive: true });
4839
5418
  const tmp = statePath(s2.sessionId) + ".tmp";
4840
- writeFileSync16(tmp, JSON.stringify(s2), "utf8");
5419
+ writeFileSync17(tmp, JSON.stringify(s2), "utf8");
4841
5420
  renameSync11(tmp, statePath(s2.sessionId));
4842
5421
  }
4843
5422
  function retireState(s2, now = Date.now()) {
@@ -4846,7 +5425,7 @@ function retireState(s2, now = Date.now()) {
4846
5425
  }
4847
5426
  function removeState(sessionId) {
4848
5427
  try {
4849
- rmSync10(statePath(sessionId), { force: true });
5428
+ rmSync11(statePath(sessionId), { force: true });
4850
5429
  } catch {
4851
5430
  }
4852
5431
  }
@@ -4855,7 +5434,7 @@ var STALE_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
4855
5434
  var STALE_NO_PID_LOG = "\u65E0\u8FDB\u7A0B\u53F7\u4E14 2 \u5C0F\u65F6\u65E0\u4E8B\u4EF6\uFF0C\u6309\u5DF2\u7ED3\u675F\u5904\u7406";
4856
5435
  function pendingApprovalSessions() {
4857
5436
  try {
4858
- const recs = JSON.parse(readFileSync18(join22(deskbuddyHome(), "approvals.json"), "utf8"));
5437
+ const recs = JSON.parse(readFileSync19(join23(deskbuddyHome(), "approvals.json"), "utf8"));
4859
5438
  return new Set(Array.isArray(recs) ? recs.filter((r) => r && r.status === "pending" && typeof r.sessionId === "string").map((r) => r.sessionId) : []);
4860
5439
  } catch {
4861
5440
  return /* @__PURE__ */ new Set();
@@ -4873,7 +5452,7 @@ function dropManagedDaemonPid(s2, args = (pid) => processArgs(pid)) {
4873
5452
  return next;
4874
5453
  }
4875
5454
  function readAliveStates(tool, opts = {}) {
4876
- if (!existsSync16(stateDir())) return [];
5455
+ if (!existsSync18(stateDir())) return [];
4877
5456
  const now = opts.now ?? Date.now();
4878
5457
  let pend = null;
4879
5458
  const pending = () => pend ??= pendingApprovalSessions();
@@ -4882,9 +5461,9 @@ function readAliveStates(tool, opts = {}) {
4882
5461
  if (!file.endsWith(".json")) continue;
4883
5462
  let s2;
4884
5463
  try {
4885
- s2 = JSON.parse(readFileSync18(join22(stateDir(), file), "utf8"));
5464
+ s2 = JSON.parse(readFileSync19(join23(stateDir(), file), "utf8"));
4886
5465
  } catch {
4887
- rmSync10(join22(stateDir(), file), { force: true });
5466
+ rmSync11(join23(stateDir(), file), { force: true });
4888
5467
  continue;
4889
5468
  }
4890
5469
  if (s2.tool !== tool) continue;
@@ -4906,15 +5485,15 @@ function readAliveStates(tool, opts = {}) {
4906
5485
  return alive;
4907
5486
  }
4908
5487
  function noticeDir() {
4909
- return join22(deskbuddyHome(), "notice");
5488
+ return join23(deskbuddyHome(), "notice");
4910
5489
  }
4911
5490
  function writeNotice(sessionId, text, now = Date.now()) {
4912
5491
  try {
4913
5492
  const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
4914
5493
  if (!safe) return;
4915
- mkdirSync17(noticeDir(), { recursive: true });
4916
- const file = join22(noticeDir(), `${safe}.json`);
4917
- writeFileSync16(file + ".tmp", JSON.stringify({ text, at: now }), "utf8");
5494
+ mkdirSync18(noticeDir(), { recursive: true });
5495
+ const file = join23(noticeDir(), `${safe}.json`);
5496
+ writeFileSync17(file + ".tmp", JSON.stringify({ text, at: now }), "utf8");
4918
5497
  renameSync11(file + ".tmp", file);
4919
5498
  } catch {
4920
5499
  }
@@ -4924,7 +5503,7 @@ var ALIAS_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
4924
5503
  function resolveAlias(sessionId, now = Date.now()) {
4925
5504
  let map = {};
4926
5505
  try {
4927
- map = JSON.parse(readFileSync18(join22(deskbuddyHome(), "alias.json"), "utf8")) || {};
5506
+ map = JSON.parse(readFileSync19(join23(deskbuddyHome(), "alias.json"), "utf8")) || {};
4928
5507
  } catch {
4929
5508
  return sessionId;
4930
5509
  }
@@ -4975,8 +5554,8 @@ function buildSnapshot(states, tool, cfg, nowMs, remote) {
4975
5554
 
4976
5555
  // src/core/dirs.ts
4977
5556
  import { createHmac as createHmac2, randomBytes as randomBytes2 } from "crypto";
4978
- import { chmodSync as chmodSync8, existsSync as existsSync17, mkdirSync as mkdirSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync17 } from "fs";
4979
- import { basename as basename4, join as join23 } from "path";
5557
+ import { chmodSync as chmodSync8, existsSync as existsSync19, mkdirSync as mkdirSync19, readFileSync as readFileSync20, writeFileSync as writeFileSync18 } from "fs";
5558
+ import { basename as basename4, join as join24 } from "path";
4980
5559
 
4981
5560
  // src/core/dirFilter.ts
4982
5561
  import { homedir as homedir7, tmpdir as tmpdir2 } from "os";
@@ -5015,19 +5594,19 @@ function isReportableDir(p, opts = {}) {
5015
5594
  // src/core/dirs.ts
5016
5595
  var DIRS_REPORT_MAX = 10;
5017
5596
  function dirsPath() {
5018
- return join23(deskbuddyHome(), "dirs.json");
5597
+ return join24(deskbuddyHome(), "dirs.json");
5019
5598
  }
5020
5599
  function dirSecret() {
5021
- const file = join23(deskbuddyHome(), "dir-secret");
5600
+ const file = join24(deskbuddyHome(), "dir-secret");
5022
5601
  try {
5023
- const s3 = readFileSync19(file, "utf8").trim();
5602
+ const s3 = readFileSync20(file, "utf8").trim();
5024
5603
  if (s3) return s3;
5025
5604
  } catch {
5026
5605
  }
5027
5606
  const s2 = randomBytes2(32).toString("hex");
5028
5607
  try {
5029
- mkdirSync18(deskbuddyHome(), { recursive: true });
5030
- writeFileSync17(file, s2, { encoding: "utf8", mode: 384 });
5608
+ mkdirSync19(deskbuddyHome(), { recursive: true });
5609
+ writeFileSync18(file, s2, { encoding: "utf8", mode: 384 });
5031
5610
  chmodSync8(file, 384);
5032
5611
  } catch {
5033
5612
  }
@@ -5038,7 +5617,7 @@ function dirRef(path) {
5038
5617
  }
5039
5618
  function readMap() {
5040
5619
  try {
5041
- const m = JSON.parse(readFileSync19(dirsPath(), "utf8"));
5620
+ const m = JSON.parse(readFileSync20(dirsPath(), "utf8"));
5042
5621
  const out = {};
5043
5622
  for (const [k, v] of Object.entries(m ?? {})) if (typeof v === "number") out[k] = v;
5044
5623
  return out;
@@ -5046,7 +5625,7 @@ function readMap() {
5046
5625
  return {};
5047
5626
  }
5048
5627
  }
5049
- function recentDirs(limit = DIRS_REPORT_MAX, exists = existsSync17, reportable = (p) => isReportableDir(p)) {
5628
+ function recentDirs(limit = DIRS_REPORT_MAX, exists = existsSync19, reportable = (p) => isReportableDir(p)) {
5050
5629
  const list = Object.entries(readMap()).filter(([p]) => p && reportable(p) && exists(p)).sort((a, b) => b[1] - a[1]).slice(0, limit);
5051
5630
  const counts = /* @__PURE__ */ new Map();
5052
5631
  for (const [p] of list) counts.set(basename4(p), (counts.get(basename4(p)) ?? 0) + 1);
@@ -5056,7 +5635,7 @@ function recentDirs(limit = DIRS_REPORT_MAX, exists = existsSync17, reportable =
5056
5635
  return { path: p, ref, name: (counts.get(basename4(p)) ?? 0) > 1 ? `${name} \xB7 ${ref.slice(0, 4)}` : name, lastUsedAt: at };
5057
5636
  });
5058
5637
  }
5059
- function dirOf(ref, exists = existsSync17) {
5638
+ function dirOf(ref, exists = existsSync19) {
5060
5639
  for (const p of Object.keys(readMap())) if (dirRef(p) === ref && exists(p)) return p;
5061
5640
  return "";
5062
5641
  }
@@ -5159,8 +5738,8 @@ function isExpired(cmd, now = Date.now()) {
5159
5738
 
5160
5739
  // src/daemon/gate/approvals.ts
5161
5740
  import { createHash as createHash3 } from "crypto";
5162
- import { mkdirSync as mkdirSync19, readFileSync as readFileSync20, renameSync as renameSync12, writeFileSync as writeFileSync18 } from "fs";
5163
- import { join as join24 } from "path";
5741
+ import { mkdirSync as mkdirSync20, readFileSync as readFileSync21, renameSync as renameSync12, writeFileSync as writeFileSync19 } from "fs";
5742
+ import { join as join25 } from "path";
5164
5743
  var RETAIN_MS = 7 * 24 * 60 * 60 * 1e3;
5165
5744
  function fingerprint(toolName, toolInput) {
5166
5745
  let input = "";
@@ -5172,7 +5751,7 @@ function fingerprint(toolName, toolInput) {
5172
5751
  return createHash3("sha256").update(toolName).update("\0").update(input).digest("hex");
5173
5752
  }
5174
5753
  function storePath() {
5175
- return join24(deskbuddyHome(), "approvals.json");
5754
+ return join25(deskbuddyHome(), "approvals.json");
5176
5755
  }
5177
5756
  var ApprovalStore = class _ApprovalStore {
5178
5757
  #records;
@@ -5181,7 +5760,7 @@ var ApprovalStore = class _ApprovalStore {
5181
5760
  }
5182
5761
  static load() {
5183
5762
  try {
5184
- const raw = JSON.parse(readFileSync20(storePath(), "utf8"));
5763
+ const raw = JSON.parse(readFileSync21(storePath(), "utf8"));
5185
5764
  return new _ApprovalStore(Array.isArray(raw) ? raw : []);
5186
5765
  } catch {
5187
5766
  return new _ApprovalStore();
@@ -5190,9 +5769,9 @@ var ApprovalStore = class _ApprovalStore {
5190
5769
  save() {
5191
5770
  for (const r of this.#records) if (r.status !== "pending" && r.toolInput !== void 0) delete r.toolInput;
5192
5771
  try {
5193
- mkdirSync19(deskbuddyHome(), { recursive: true });
5772
+ mkdirSync20(deskbuddyHome(), { recursive: true });
5194
5773
  const tmp = storePath() + ".tmp";
5195
- writeFileSync18(tmp, JSON.stringify(this.#records), { encoding: "utf8", mode: 384 });
5774
+ writeFileSync19(tmp, JSON.stringify(this.#records), { encoding: "utf8", mode: 384 });
5196
5775
  renameSync12(tmp, storePath());
5197
5776
  } catch {
5198
5777
  }
@@ -5311,7 +5890,7 @@ function mapStatus(s2, decision) {
5311
5890
  import { randomUUID as randomUUID3 } from "crypto";
5312
5891
  import { lstatSync as lstatSync2, readlinkSync, realpathSync as realpathSync3 } from "fs";
5313
5892
  import { homedir as homedir8 } from "os";
5314
- import { basename as basename6, dirname as dirname14, join as join25, posix as posix3, resolve as resolve5, win32 as win324 } from "path";
5893
+ import { basename as basename6, dirname as dirname16, join as join26, posix as posix3, resolve as resolve5, win32 as win324 } from "path";
5315
5894
 
5316
5895
  // src/daemon/gate/classify.ts
5317
5896
  import { isAbsolute, relative as relative2, resolve as resolve4, sep as sep2 } from "path";
@@ -5466,7 +6045,7 @@ function editInProject(toolName, toolInput, cwd, opts) {
5466
6045
  }
5467
6046
 
5468
6047
  // src/daemon/gate/question.ts
5469
- import { closeSync as closeSync5, openSync as openSync5, readSync as readSync4, statSync as statSync7 } from "fs";
6048
+ import { closeSync as closeSync5, openSync as openSync5, readSync as readSync4, statSync as statSync8 } from "fs";
5470
6049
  var MAX_QUESTIONS = 4;
5471
6050
  var MAX_OPTIONS = 4;
5472
6051
  var HEADER_MAX = 12;
@@ -5573,7 +6152,7 @@ function lastAssistantText(transcriptPath) {
5573
6152
  if (typeof transcriptPath !== "string" || !transcriptPath) return "";
5574
6153
  let text = "";
5575
6154
  try {
5576
- const size = statSync7(transcriptPath).size;
6155
+ const size = statSync8(transcriptPath).size;
5577
6156
  const len = Math.min(size, TAIL_BYTES);
5578
6157
  const buf = Buffer.alloc(len);
5579
6158
  const fd = openSync5(transcriptPath, "r");
@@ -5586,9 +6165,9 @@ function lastAssistantText(transcriptPath) {
5586
6165
  } catch {
5587
6166
  return "";
5588
6167
  }
5589
- const lines2 = text.split("\n");
5590
- for (let i = lines2.length - 1; i >= 0; i--) {
5591
- const line = lines2[i].trim();
6168
+ const lines3 = text.split("\n");
6169
+ for (let i = lines3.length - 1; i >= 0; i--) {
6170
+ const line = lines3[i].trim();
5592
6171
  if (!line.startsWith("{")) continue;
5593
6172
  try {
5594
6173
  const m = JSON.parse(line);
@@ -5847,6 +6426,8 @@ var CODEX_TEXT = {
5847
6426
  yes: "Yes, proceed",
5848
6427
  cmdSession: "Yes, and don't ask again for this command in this session",
5849
6428
  cmdPrefix: "Yes, and don't ask again for commands that start with `",
6429
+ /** 前缀放不进一行(Windows PowerShell 带路径等)时的选项名:TUI 原文的前半句,前缀进 description */
6430
+ cmdPrefixHead: "Yes, and don't ask again for commands that start with",
5850
6431
  fileSession: "Yes, and don't ask again for these files",
5851
6432
  decline: "No, continue without running it",
5852
6433
  cancel: "No, and tell Codex what to do differently"
@@ -5874,6 +6455,15 @@ function renderAmendment(v) {
5874
6455
  if (a.length === 3 && /(^|\/)(ba|z)?sh$/.test(a[0]) && (a[1] === "-lc" || a[1] === "-c")) return a[2];
5875
6456
  return a.every((x) => /^[A-Za-z0-9_@%+=:,./-]+$/.test(x)) ? a.join(" ") : null;
5876
6457
  }
6458
+ function renderAmendmentQuoted(v) {
6459
+ if (!Array.isArray(v) || v.length === 0 || v.some((x) => typeof x !== "string" || x === "")) return null;
6460
+ const out = [];
6461
+ for (const x of v) {
6462
+ if (/["\u0000-\u001f\u007f-\u009f​-‏‪-‮⁦-⁩]/.test(x)) return null;
6463
+ out.push(/^[A-Za-z0-9_@%+=,./-]+$/.test(x) ? x : `"${x}"`);
6464
+ }
6465
+ return out.join(" ");
6466
+ }
5877
6467
  function codexCommandOptions(p) {
5878
6468
  if (p.kind !== void 0 && p.kind !== null && p.kind !== "command") return null;
5879
6469
  if (p.networkApprovalContext) return null;
@@ -5894,9 +6484,17 @@ function codexCommandOptions(p) {
5894
6484
  else if (d && typeof d === "object" && !Array.isArray(d) && Object.keys(d).length === 1 && "acceptWithExecpolicyAmendment" in d) {
5895
6485
  const amend = d.acceptWithExecpolicyAmendment?.execpolicy_amendment;
5896
6486
  const shown = renderAmendment(amend);
5897
- if (shown === null) return null;
6487
+ const whole = shown === null ? null : `${CODEX_TEXT.cmdPrefix}${shown}\``;
6488
+ let opt;
6489
+ if (whole !== null && whole.length <= OPTION_LABEL_MAX) {
6490
+ opt = { id: "always", label: whole, kind: "allow_always" };
6491
+ } else {
6492
+ const desc = renderAmendmentQuoted(amend);
6493
+ if (desc === null || desc.length > OPTION_DESC_MAX) return null;
6494
+ opt = { id: "always", label: CODEX_TEXT.cmdPrefixHead, description: desc, kind: "allow_always" };
6495
+ }
5898
6496
  add(
5899
- { id: "always", label: `${CODEX_TEXT.cmdPrefix}${shown}\``, kind: "allow_always" },
6497
+ opt,
5900
6498
  { decision: "allow", native: { decision: { acceptWithExecpolicyAmendment: { execpolicy_amendment: [...amend] } } } }
5901
6499
  );
5902
6500
  } else return null;
@@ -5991,7 +6589,9 @@ function qoderCnTuiOptions(toolName, toolInput, suggestions) {
5991
6589
  const desc = `\u89C4\u5219\uFF1ABash(${rc}) \u2192 localSettings`;
5992
6590
  if (rc === cmd && /&&|\|\||;|\|/.test(cmd)) return qoderSet(...remember("always", QODER_TEXT.exact, "allow_always", desc));
5993
6591
  const root = cmd.split(/\s+/)[0];
5994
- if (!rc.endsWith(" *") || !/^[A-Za-z0-9_.+-]+$/.test(root) || !(rc === `${root} *` || rc.startsWith(`${root} `))) return null;
6592
+ const spaced = rc.endsWith(" *") && (rc === `${root} *` || rc.startsWith(`${root} `));
6593
+ const colon = rc === `${root}:*`;
6594
+ if (!/^[A-Za-z0-9_.+-]+$/.test(root) || !(spaced || colon)) return null;
5995
6595
  return qoderSet(...remember("always", alwaysFor(root), "allow_always", desc));
5996
6596
  }
5997
6597
  if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit") {
@@ -6966,18 +7566,18 @@ function realpathNearest(p) {
6966
7566
  }
6967
7567
  if (exists) {
6968
7568
  try {
6969
- return join25(realpathSync3(cur), ...[...tail2].reverse());
7569
+ return join26(realpathSync3(cur), ...[...tail2].reverse());
6970
7570
  } catch {
6971
7571
  if (!isLink) return "";
6972
7572
  try {
6973
- cur = resolve5(dirname14(cur), readlinkSync(cur));
7573
+ cur = resolve5(dirname16(cur), readlinkSync(cur));
6974
7574
  } catch {
6975
7575
  return "";
6976
7576
  }
6977
7577
  continue;
6978
7578
  }
6979
7579
  }
6980
- const parent = dirname14(cur);
7580
+ const parent = dirname16(cur);
6981
7581
  if (parent === cur) return "";
6982
7582
  tail2.push(basename6(cur));
6983
7583
  cur = parent;
@@ -6996,11 +7596,11 @@ function cwdForms(cwd) {
6996
7596
  // src/daemon/gate/server.ts
6997
7597
  import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
6998
7598
  import { createServer } from "http";
6999
- import { mkdirSync as mkdirSync20, renameSync as renameSync13, rmSync as rmSync11, writeFileSync as writeFileSync19 } from "fs";
7000
- import { join as join26 } from "path";
7599
+ import { mkdirSync as mkdirSync21, renameSync as renameSync13, rmSync as rmSync12, writeFileSync as writeFileSync20 } from "fs";
7600
+ import { join as join27 } from "path";
7001
7601
  var MAX_BODY_BYTES = 8 * 1024 * 1024;
7002
7602
  function gateConfPath() {
7003
- return join26(deskbuddyHome(), "gate.json");
7603
+ return join27(deskbuddyHome(), "gate.json");
7004
7604
  }
7005
7605
  function tokenEquals(a, b) {
7006
7606
  const x = Buffer.from(a);
@@ -7036,9 +7636,9 @@ var GateServer = class {
7036
7636
  this.#server = server;
7037
7637
  const addr = server.address();
7038
7638
  const port = typeof addr === "object" && addr ? addr.port : 0;
7039
- mkdirSync20(deskbuddyHome(), { recursive: true });
7639
+ mkdirSync21(deskbuddyHome(), { recursive: true });
7040
7640
  const tmp = gateConfPath() + ".tmp";
7041
- writeFileSync19(tmp, JSON.stringify({ port, token: this.#token, pid: process.pid }), {
7641
+ writeFileSync20(tmp, JSON.stringify({ port, token: this.#token, pid: process.pid }), {
7042
7642
  encoding: "utf8",
7043
7643
  mode: 384
7044
7644
  });
@@ -7047,7 +7647,7 @@ var GateServer = class {
7047
7647
  }
7048
7648
  async stop() {
7049
7649
  try {
7050
- rmSync11(gateConfPath(), { force: true });
7650
+ rmSync12(gateConfPath(), { force: true });
7051
7651
  } catch {
7052
7652
  }
7053
7653
  const s2 = this.#server;
@@ -7176,15 +7776,15 @@ function buildHandshakeUrl(cfg, opts = {}) {
7176
7776
  }
7177
7777
 
7178
7778
  // src/daemon/log.ts
7179
- import { appendFileSync, mkdirSync as mkdirSync21, statSync as statSync8, truncateSync } from "fs";
7180
- import { join as join27 } from "path";
7779
+ import { appendFileSync, mkdirSync as mkdirSync22, statSync as statSync9, truncateSync } from "fs";
7780
+ import { join as join28 } from "path";
7181
7781
  var MAX_LOG_BYTES = 2 * 1024 * 1024;
7182
7782
  function logPath() {
7183
- return join27(deskbuddyHome(), "daemon.log");
7783
+ return join28(deskbuddyHome(), "daemon.log");
7184
7784
  }
7185
7785
  function rotateIfNeeded(path) {
7186
7786
  try {
7187
- if (statSync8(path).size > MAX_LOG_BYTES) truncateSync(path, 0);
7787
+ if (statSync9(path).size > MAX_LOG_BYTES) truncateSync(path, 0);
7188
7788
  } catch {
7189
7789
  }
7190
7790
  }
@@ -7192,7 +7792,7 @@ function log(line) {
7192
7792
  const stamped = `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
7193
7793
  `;
7194
7794
  try {
7195
- mkdirSync21(deskbuddyHome(), { recursive: true });
7795
+ mkdirSync22(deskbuddyHome(), { recursive: true });
7196
7796
  const p = logPath();
7197
7797
  rotateIfNeeded(p);
7198
7798
  appendFileSync(p, stamped, "utf8");
@@ -7284,11 +7884,11 @@ function failureMessage(reason) {
7284
7884
  }
7285
7885
 
7286
7886
  // src/daemon/seen.ts
7287
- import { mkdirSync as mkdirSync22, readFileSync as readFileSync21, renameSync as renameSync14, writeFileSync as writeFileSync20 } from "fs";
7288
- import { join as join28 } from "path";
7887
+ import { mkdirSync as mkdirSync23, readFileSync as readFileSync22, renameSync as renameSync14, writeFileSync as writeFileSync21 } from "fs";
7888
+ import { join as join29 } from "path";
7289
7889
  var SEEN_CAPACITY = 200;
7290
7890
  function seenPath() {
7291
- return join28(deskbuddyHome(), "seen.json");
7891
+ return join29(deskbuddyHome(), "seen.json");
7292
7892
  }
7293
7893
  var SeenIds = class _SeenIds {
7294
7894
  #ids = [];
@@ -7319,7 +7919,7 @@ var SeenIds = class _SeenIds {
7319
7919
  }
7320
7920
  static load() {
7321
7921
  try {
7322
- const raw = JSON.parse(readFileSync21(seenPath(), "utf8"));
7922
+ const raw = JSON.parse(readFileSync22(seenPath(), "utf8"));
7323
7923
  return new _SeenIds(Array.isArray(raw?.ids) ? raw.ids.filter((x) => typeof x === "string") : []);
7324
7924
  } catch {
7325
7925
  return new _SeenIds();
@@ -7327,9 +7927,9 @@ var SeenIds = class _SeenIds {
7327
7927
  }
7328
7928
  save() {
7329
7929
  try {
7330
- mkdirSync22(deskbuddyHome(), { recursive: true });
7930
+ mkdirSync23(deskbuddyHome(), { recursive: true });
7331
7931
  const tmp = seenPath() + ".tmp";
7332
- writeFileSync20(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
7932
+ writeFileSync21(tmp, JSON.stringify({ ids: this.#ids }), "utf8");
7333
7933
  renameSync14(tmp, seenPath());
7334
7934
  } catch {
7335
7935
  }
@@ -7337,9 +7937,9 @@ var SeenIds = class _SeenIds {
7337
7937
  };
7338
7938
 
7339
7939
  // src/daemon/qwenworkBridge.ts
7340
- import { readFileSync as readFileSync22 } from "fs";
7940
+ import { readFileSync as readFileSync23 } from "fs";
7341
7941
  import { homedir as homedir9 } from "os";
7342
- import { basename as basename7, dirname as dirname15, join as join29, resolve as resolve6 } from "path";
7942
+ import { basename as basename7, dirname as dirname17, join as join30, resolve as resolve6 } from "path";
7343
7943
  var SUBMIT_TOOL = "qwenwork_task_submit_response";
7344
7944
  var DETAIL_TOOL = "qwenwork_task_get_detail";
7345
7945
  var POLL_MS = 2e3;
@@ -7386,7 +7986,7 @@ function sameDir(a, b) {
7386
7986
  function chatIdOf(cwd, root) {
7387
7987
  if (typeof cwd !== "string" || !cwd) return null;
7388
7988
  const dir = resolve6(cwd);
7389
- if (!sameDir(dirname15(dir), resolve6(root))) return null;
7989
+ if (!sameDir(dirname17(dir), resolve6(root))) return null;
7390
7990
  const id = basename7(dir);
7391
7991
  return /^[A-Za-z0-9_-]{1,64}$/.test(id) ? id : null;
7392
7992
  }
@@ -7537,7 +8137,7 @@ var QwenWorkBridge = class {
7537
8137
  #stopped = false;
7538
8138
  constructor(deps2) {
7539
8139
  this.#d = deps2;
7540
- const read = deps2.readConfig ?? (() => readConfigFile(join29(qwenworkHome(), "mcp-adaptor.config")));
8140
+ const read = deps2.readConfig ?? (() => readConfigFile(join30(qwenworkHome(), "mcp-adaptor.config")));
7541
8141
  const f = deps2.fetch ?? globalThis.fetch;
7542
8142
  this.#mcp = new QwenWorkMcp(read, f, deps2.log);
7543
8143
  }
@@ -7586,7 +8186,7 @@ var QwenWorkBridge = class {
7586
8186
  toolUseId = typeof det.toolUseId === "string" ? det.toolUseId : "";
7587
8187
  if (!toolName) return;
7588
8188
  } else return;
7589
- const root = this.#d.workspaceRoot ?? join29(homedir9(), ".qwenworkcn", "workspace");
8189
+ const root = this.#d.workspaceRoot ?? join30(homedir9(), ".qwenworkcn", "workspace");
7590
8190
  const chatId = chatIdOf(h.cwd, root);
7591
8191
  if (!chatId) {
7592
8192
  this.#d.log("\u5343\u95EE\u529E\u516C\uFF1A\u8FD9\u9053\u9898 / \u6388\u6743\u4E0D\u5728\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF08\u53EF\u80FD\u662F\u5B50\u4EE3\u7406\uFF09\uFF0C\u4E0D\u63A8\u8BBE\u5907");
@@ -7800,7 +8400,7 @@ function detailSettled(text, toolUseId) {
7800
8400
  }
7801
8401
  function readConfigFile(p) {
7802
8402
  try {
7803
- return readFileSync22(p, "utf8");
8403
+ return readFileSync23(p, "utf8");
7804
8404
  } catch {
7805
8405
  return null;
7806
8406
  }
@@ -7810,13 +8410,13 @@ function sleep(ms) {
7810
8410
  }
7811
8411
 
7812
8412
  // src/daemon/workbuddyBridge.ts
7813
- import { closeSync as closeSync6, openSync as openSync6, readSync as readSync5, realpathSync as realpathSync4, statSync as statSync10 } from "fs";
7814
- import { dirname as dirname16, sep as sep3 } from "path";
8413
+ import { closeSync as closeSync6, openSync as openSync6, readSync as readSync5, realpathSync as realpathSync4, statSync as statSync11 } from "fs";
8414
+ import { dirname as dirname18, sep as sep3 } from "path";
7815
8415
 
7816
8416
  // src/core/wrapWindows.ts
7817
- import { chmodSync as chmodSync9, mkdirSync as mkdirSync23, readdirSync as readdirSync7, readFileSync as readFileSync23, renameSync as renameSync15, rmSync as rmSync12, statSync as statSync9, writeFileSync as writeFileSync21 } from "fs";
8417
+ import { chmodSync as chmodSync9, mkdirSync as mkdirSync24, readdirSync as readdirSync7, readFileSync as readFileSync24, renameSync as renameSync15, rmSync as rmSync13, statSync as statSync10, writeFileSync as writeFileSync22 } from "fs";
7818
8418
  import { tmpdir as tmpdir3 } from "os";
7819
- import { join as join30 } from "path";
8419
+ import { join as join31 } from "path";
7820
8420
  var WINDOW_ID_RE = /^[a-f0-9]{32}$/;
7821
8421
  var WINDOW_EVENTS = [
7822
8422
  "SessionStart",
@@ -7829,24 +8429,24 @@ var WINDOW_EVENTS = [
7829
8429
  "SessionEnd"
7830
8430
  ];
7831
8431
  function windowsDir() {
7832
- return join30(deskbuddyHome(), "windows");
8432
+ return join31(deskbuddyHome(), "windows");
7833
8433
  }
7834
8434
  function bySessionDir() {
7835
- return join30(windowsDir(), "by-session");
8435
+ return join31(windowsDir(), "by-session");
7836
8436
  }
7837
8437
  function safeSid2(sessionId) {
7838
8438
  return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
7839
8439
  }
7840
8440
  function ensureDir(dir) {
7841
- mkdirSync23(dir, { recursive: true, mode: 448 });
8441
+ mkdirSync24(dir, { recursive: true, mode: 448 });
7842
8442
  }
7843
8443
  function atomicWrite2(file, text) {
7844
8444
  const tmp = `${file}.${process.pid}.tmp`;
7845
- writeFileSync21(tmp, text, { encoding: "utf8", mode: 384 });
8445
+ writeFileSync22(tmp, text, { encoding: "utf8", mode: 384 });
7846
8446
  renameSync15(tmp, file);
7847
8447
  }
7848
8448
  function windowFile(windowId) {
7849
- return join30(windowsDir(), `${windowId}.json`);
8449
+ return join31(windowsDir(), `${windowId}.json`);
7850
8450
  }
7851
8451
  function writeWindow(windowId, rec) {
7852
8452
  if (!WINDOW_ID_RE.test(windowId)) return;
@@ -7861,7 +8461,7 @@ function windowSecret(windowId) {
7861
8461
  function readWindow(windowId) {
7862
8462
  if (!WINDOW_ID_RE.test(windowId)) return null;
7863
8463
  try {
7864
- const r = JSON.parse(readFileSync23(windowFile(windowId), "utf8"));
8464
+ const r = JSON.parse(readFileSync24(windowFile(windowId), "utf8"));
7865
8465
  return r && typeof r.pid === "number" ? r : null;
7866
8466
  } catch {
7867
8467
  return null;
@@ -7871,7 +8471,7 @@ function removeWindow(windowId) {
7871
8471
  if (!WINDOW_ID_RE.test(windowId)) return;
7872
8472
  for (const f of [windowFile(windowId), lastEventFile(windowId)]) {
7873
8473
  try {
7874
- rmSync12(f, { force: true });
8474
+ rmSync13(f, { force: true });
7875
8475
  } catch {
7876
8476
  }
7877
8477
  }
@@ -7882,9 +8482,9 @@ function removeWindow(windowId) {
7882
8482
  return;
7883
8483
  }
7884
8484
  for (const n of names) {
7885
- const f = join30(bySessionDir(), n);
8485
+ const f = join31(bySessionDir(), n);
7886
8486
  try {
7887
- if (readFileSync23(f, "utf8").trim() === windowId) rmSync12(f, { force: true });
8487
+ if (readFileSync24(f, "utf8").trim() === windowId) rmSync13(f, { force: true });
7888
8488
  } catch {
7889
8489
  }
7890
8490
  }
@@ -7893,19 +8493,19 @@ function readBySession(sessionId) {
7893
8493
  const safe = safeSid2(sessionId);
7894
8494
  if (!safe) return null;
7895
8495
  try {
7896
- const id = readFileSync23(join30(bySessionDir(), safe), "utf8").trim();
8496
+ const id = readFileSync24(join31(bySessionDir(), safe), "utf8").trim();
7897
8497
  return WINDOW_ID_RE.test(id) ? id : null;
7898
8498
  } catch {
7899
8499
  return null;
7900
8500
  }
7901
8501
  }
7902
8502
  function lastEventFile(windowId) {
7903
- return join30(windowsDir(), `${windowId}.last`);
8503
+ return join31(windowsDir(), `${windowId}.last`);
7904
8504
  }
7905
8505
  function readLastEvent(windowId) {
7906
8506
  if (!WINDOW_ID_RE.test(windowId)) return null;
7907
8507
  try {
7908
- const e = JSON.parse(readFileSync23(lastEventFile(windowId), "utf8"));
8508
+ const e = JSON.parse(readFileSync24(lastEventFile(windowId), "utf8"));
7909
8509
  return e && typeof e.ev === "string" && typeof e.at === "number" ? { ev: e.ev, sid: String(e.sid ?? ""), at: e.at } : null;
7910
8510
  } catch {
7911
8511
  return null;
@@ -7919,16 +8519,16 @@ function writeLastEvent(windowId, ev, sid, at) {
7919
8519
  function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
7920
8520
  if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
7921
8521
  const name = `${windowId.slice(0, 16)}.sock`;
7922
- const primary = join30(home, "win", name);
8522
+ const primary = join31(home, "win", name);
7923
8523
  if (Buffer.byteLength(primary) <= 103) return primary;
7924
8524
  const uid = typeof process.getuid === "function" ? process.getuid() : 0;
7925
- return join30(tmpdir3(), `deskbuddy-${uid}`, name);
8525
+ return join31(tmpdir3(), `deskbuddy-${uid}`, name);
7926
8526
  }
7927
8527
  function ensureChannelDir(sockPath) {
7928
- const dir = join30(sockPath, "..");
8528
+ const dir = join31(sockPath, "..");
7929
8529
  try {
7930
- mkdirSync23(dir, { recursive: true, mode: 448 });
7931
- const st = statSync9(dir);
8530
+ mkdirSync24(dir, { recursive: true, mode: 448 });
8531
+ const st = statSync10(dir);
7932
8532
  if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
7933
8533
  chmodSync9(dir, 448);
7934
8534
  return true;
@@ -7948,8 +8548,8 @@ var FINGERPRINT_CHARS = 40;
7948
8548
  var FINGERPRINT_TAIL = 30;
7949
8549
  var FP_SEP = "\u2026";
7950
8550
  var CURSOR = "[\u276F>\u203A]";
7951
- function joined(lines2) {
7952
- return lines2.join("\n");
8551
+ function joined(lines3) {
8552
+ return lines3.join("\n");
7953
8553
  }
7954
8554
  function squash(s2) {
7955
8555
  return s2.replace(/[\s\u2500-\u257F]+/g, "");
@@ -7988,10 +8588,10 @@ async function waitUntil(io, ok, ms = CONFIRM_MS) {
7988
8588
  }
7989
8589
  return ok(io.screen());
7990
8590
  }
7991
- function permissionRegion(lines2, toolName) {
8591
+ function permissionRegion(lines3, toolName) {
7992
8592
  let q = -1;
7993
- for (let i = lines2.length - 1; i >= 0; i--) {
7994
- if (/^\s*Do you want to .+\?\s*$/.test(lines2[i])) {
8593
+ for (let i = lines3.length - 1; i >= 0; i--) {
8594
+ if (/^\s*Do you want to .+\?\s*$/.test(lines3[i])) {
7995
8595
  q = i;
7996
8596
  break;
7997
8597
  }
@@ -8000,21 +8600,21 @@ function permissionRegion(lines2, toolName) {
8000
8600
  const head = new RegExp(`^\\s*\u25CF\\s*${toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\(`);
8001
8601
  let h = -1;
8002
8602
  for (let i = q - 1; i >= 0; i--) {
8003
- if (head.test(lines2[i])) {
8603
+ if (head.test(lines3[i])) {
8004
8604
  h = i;
8005
8605
  break;
8006
8606
  }
8007
- if (/^\s*●/.test(lines2[i])) return null;
8607
+ if (/^\s*●/.test(lines3[i])) return null;
8008
8608
  }
8009
8609
  if (h < 0) return null;
8010
- const after = joined(lines2.slice(q + 1, q + 6));
8610
+ const after = joined(lines3.slice(q + 1, q + 6));
8011
8611
  if (!new RegExp(`^\\s*(?:${CURSOR}\\s*)?1\\.\\s+Yes\\s*$`, "m").test(after)) return null;
8012
8612
  if (!/^\s*(?:[❯>›]\s*)?3\.\s+No\b/m.test(after)) return null;
8013
- return lines2.slice(h, q + 1);
8613
+ return lines3.slice(h, q + 1);
8014
8614
  }
8015
- function isPermissionDialog(lines2, toolName, fingerprint2) {
8615
+ function isPermissionDialog(lines3, toolName, fingerprint2) {
8016
8616
  if (!fingerprint2) return false;
8017
- const region = permissionRegion(lines2, toolName);
8617
+ const region = permissionRegion(lines3, toolName);
8018
8618
  if (!region) return false;
8019
8619
  const text = squash(joined(region));
8020
8620
  return fingerprint2.split(FP_SEP).every((part) => !!part && text.includes(squash(part)));
@@ -8026,42 +8626,42 @@ async function answerPermission(io, toolName, fingerprint2, decision) {
8026
8626
  return await waitUntil(io, (l) => !ok(l)) ? "done" : "partial";
8027
8627
  }
8028
8628
  var TAB_ROW = /←.*[☐☒].*Submit.*→/;
8029
- function isQuestionDialog(lines2) {
8030
- return lines2.some((l) => TAB_ROW.test(l));
8629
+ function isQuestionDialog(lines3) {
8630
+ return lines3.some((l) => TAB_ROW.test(l));
8031
8631
  }
8032
- function onQuestion(lines2, q) {
8033
- const start = questionStart(lines2);
8632
+ function onQuestion(lines3, q) {
8633
+ const start = questionStart(lines3);
8034
8634
  if (start < 0) return false;
8035
8635
  const t = squash(q.question);
8036
- return !!t && squash(joined(lines2.slice(start))).includes(t);
8636
+ return !!t && squash(joined(lines3.slice(start))).includes(t);
8037
8637
  }
8038
- function isReviewPage(lines2) {
8039
- const t = joined(lines2);
8638
+ function isReviewPage(lines3) {
8639
+ const t = joined(lines3);
8040
8640
  return /Review your answers/.test(t) && /Submit answers/.test(t);
8041
8641
  }
8042
- function questionStart(lines2) {
8043
- for (let i = lines2.length - 1; i >= 0; i--) if (TAB_ROW.test(lines2[i])) return i + 1;
8642
+ function questionStart(lines3) {
8643
+ for (let i = lines3.length - 1; i >= 0; i--) if (TAB_ROW.test(lines3[i])) return i + 1;
8044
8644
  return -1;
8045
8645
  }
8046
- function optionLine(lines2, n, label2) {
8646
+ function optionLine(lines3, n, label2) {
8047
8647
  const lab = squash(label2).slice(0, 6);
8048
- const start = questionStart(lines2);
8648
+ const start = questionStart(lines3);
8049
8649
  if (start < 0) return -1;
8050
- for (let i = start; i < lines2.length; i++) {
8051
- const m = lines2[i].match(/^\s*(?:[❯>›]\s*)?[☐☒]?\s*(\d+)\.\s+(.*)$/);
8650
+ for (let i = start; i < lines3.length; i++) {
8651
+ const m = lines3[i].match(/^\s*(?:[❯>›]\s*)?[☐☒]?\s*(\d+)\.\s+(.*)$/);
8052
8652
  if (m && Number(m[1]) === n && squash(m[2]).startsWith(lab)) return i;
8053
8653
  }
8054
8654
  return -1;
8055
8655
  }
8056
- function checked(lines2, line) {
8057
- return line >= 0 && /^\s*☒/.test(lines2[line]);
8656
+ function checked(lines3, line) {
8657
+ return line >= 0 && /^\s*☒/.test(lines3[line]);
8058
8658
  }
8059
- function singleCursor(lines2, q) {
8659
+ function singleCursor(lines3, q) {
8060
8660
  const n = q.options.length;
8061
- const start = questionStart(lines2);
8661
+ const start = questionStart(lines3);
8062
8662
  if (start < 0) return -1;
8063
- for (let i = start; i < lines2.length; i++) {
8064
- const l = lines2[i];
8663
+ for (let i = start; i < lines3.length; i++) {
8664
+ const l = lines3[i];
8065
8665
  const m = l.match(new RegExp(`^\\s*${CURSOR}\\s*(\\d+)\\.\\s+(.*)$`));
8066
8666
  if (m) {
8067
8667
  const k = Number(m[1]) - 1;
@@ -8138,14 +8738,14 @@ async function answerQuestions(io, questions, selections) {
8138
8738
  io.write(KEY_ENTER);
8139
8739
  return await waitUntil(io, (l) => !isReviewPage(l) && !isQuestionDialog(l)) ? "done" : "partial";
8140
8740
  }
8141
- function stillWaiting(lines2, probe) {
8142
- if (probe.questions && probe.questions.length > 0) return probe.questions.some((q) => onQuestion(lines2, q)) || isReviewPage(lines2);
8143
- return isPermissionDialog(lines2, probe.toolName, probe.fingerprint ?? "");
8741
+ function stillWaiting(lines3, probe) {
8742
+ if (probe.questions && probe.questions.length > 0) return probe.questions.some((q) => onQuestion(lines3, q)) || isReviewPage(lines3);
8743
+ return isPermissionDialog(lines3, probe.toolName, probe.fingerprint ?? "");
8144
8744
  }
8145
8745
 
8146
8746
  // src/wrap/channel.ts
8147
8747
  import { randomBytes as randomBytes5 } from "crypto";
8148
- import { chmodSync as chmodSync10, rmSync as rmSync13 } from "fs";
8748
+ import { chmodSync as chmodSync10, rmSync as rmSync14 } from "fs";
8149
8749
  import { connect, createServer as createServer2 } from "net";
8150
8750
  function str3(v, max) {
8151
8751
  return typeof v === "string" && v.length > 0 && v.length <= max ? v : null;
@@ -8281,13 +8881,13 @@ function replyMac(secret, nonceC, r) {
8281
8881
  function startChannelServer(path, secret, onRequest, platform = process.platform, onAnswer) {
8282
8882
  return new Promise((resolve10) => {
8283
8883
  const server = createServer2((sock) => {
8284
- let open = true;
8884
+ let open2 = true;
8285
8885
  let phase = "hello";
8286
8886
  let nonceC = "";
8287
8887
  let nonceS = "";
8288
8888
  sock.on("error", () => {
8289
8889
  });
8290
- sock.on("close", () => open = false);
8890
+ sock.on("close", () => open2 = false);
8291
8891
  const authTimer = setTimeout(() => {
8292
8892
  if (phase === "hello") sock.destroy();
8293
8893
  }, 5e3);
@@ -8332,7 +8932,7 @@ function startChannelServer(path, secret, onRequest, platform = process.platform
8332
8932
  return;
8333
8933
  }
8334
8934
  phase = "done";
8335
- onRequest(req2, reply, () => open);
8935
+ onRequest(req2, reply, () => open2);
8336
8936
  } catch {
8337
8937
  sock.destroy();
8338
8938
  }
@@ -8340,7 +8940,7 @@ function startChannelServer(path, secret, onRequest, platform = process.platform
8340
8940
  });
8341
8941
  if (platform !== "win32") {
8342
8942
  try {
8343
- rmSync13(path, { force: true });
8943
+ rmSync14(path, { force: true });
8344
8944
  } catch {
8345
8945
  }
8346
8946
  }
@@ -8373,7 +8973,7 @@ function closeChannelServer(server, path, platform = process.platform) {
8373
8973
  }
8374
8974
  if (platform !== "win32") {
8375
8975
  try {
8376
- rmSync13(path, { force: true });
8976
+ rmSync14(path, { force: true });
8377
8977
  } catch {
8378
8978
  }
8379
8979
  }
@@ -8489,7 +9089,7 @@ function sendAnswer(path, secret, req2, opts = {}) {
8489
9089
  }
8490
9090
 
8491
9091
  // src/daemon/workbuddyBridge.ts
8492
- function transcriptAllowed(path, root = dirname16(workbuddySettingsPath()), realpath = realpathSync4) {
9092
+ function transcriptAllowed(path, root = dirname18(workbuddySettingsPath()), realpath = realpathSync4) {
8493
9093
  if (!path) return false;
8494
9094
  let real2;
8495
9095
  let base;
@@ -8508,7 +9108,7 @@ var SEEN_TTL_MS2 = 60 * 60 * 1e3;
8508
9108
  var ANSWER_TTL_MS = 60 * 1e3;
8509
9109
  function tail(path) {
8510
9110
  try {
8511
- const size = statSync10(path).size;
9111
+ const size = statSync11(path).size;
8512
9112
  const len = Math.min(size, TAIL_BYTES2);
8513
9113
  const buf = Buffer.alloc(len);
8514
9114
  const fd = openSync6(path, "r");
@@ -8565,10 +9165,10 @@ function toolOfMessage(message) {
8565
9165
  }
8566
9166
  function pendingCall(text, toolName) {
8567
9167
  const { calls, done } = scanTranscript(text);
8568
- const open = calls.filter((c) => !done.has(c.callId));
8569
- const last = open[open.length - 1];
9168
+ const open2 = calls.filter((c) => !done.has(c.callId));
9169
+ const last = open2[open2.length - 1];
8570
9170
  if (!last || last.name !== toolName) return null;
8571
- if (open.filter((c) => c.name === toolName).length > 1) return null;
9171
+ if (open2.filter((c) => c.name === toolName).length > 1) return null;
8572
9172
  return last;
8573
9173
  }
8574
9174
  function selectionsOf(input, lists) {
@@ -8624,7 +9224,7 @@ var WorkbuddyBridge = class {
8624
9224
  return null;
8625
9225
  }
8626
9226
  if (!rec || rec.answer !== true || rec.tool !== "workbuddy") return null;
8627
- return (this.#d.alive ?? pidAlive2)(rec.pid) ? rec : null;
9227
+ return (this.#d.alive ?? pidAlive3)(rec.pid) ? rec : null;
8628
9228
  }
8629
9229
  onNotify(req2) {
8630
9230
  void this.#onNotify(req2).catch((e) => this.#d.log(`WorkBuddy \u63A8\u5361\u51FA\u9519\uFF1A${e?.message ?? e}`));
@@ -8832,7 +9432,7 @@ var WorkbuddyBridge = class {
8832
9432
  }
8833
9433
  }
8834
9434
  };
8835
- function pidAlive2(pid) {
9435
+ function pidAlive3(pid) {
8836
9436
  if (!Number.isInteger(pid) || pid <= 0) return false;
8837
9437
  try {
8838
9438
  process.kill(pid, 0);
@@ -8847,19 +9447,19 @@ function defaultSend(windowId, rec, req2) {
8847
9447
  }
8848
9448
 
8849
9449
  // src/wrap/trustPrompt.ts
8850
- import { existsSync as existsSync18, realpathSync as realpathSync5, statSync as statSync11 } from "fs";
9450
+ import { existsSync as existsSync20, realpathSync as realpathSync5, statSync as statSync12 } from "fs";
8851
9451
  import { homedir as homedir10 } from "os";
8852
- import { dirname as dirname17, join as join31, resolve as resolve7 } from "path";
9452
+ import { dirname as dirname19, join as join32, resolve as resolve7 } from "path";
8853
9453
  var TRUST_TOOLS = /* @__PURE__ */ new Set(["claude_code", "workbuddy", "codex"]);
8854
9454
  var CURSOR_CHARS = "\u276F>\u203A";
8855
9455
  var BOX = /[─-╿|]/g;
8856
9456
  function squash2(s2) {
8857
9457
  return s2.replace(BOX, "").replace(/\s+/g, "");
8858
9458
  }
8859
- function squashLines(lines2) {
9459
+ function squashLines(lines3) {
8860
9460
  let body = "";
8861
9461
  const breaks = [];
8862
- for (const line of lines2) {
9462
+ for (const line of lines3) {
8863
9463
  for (const ch of line) {
8864
9464
  if (/\s/.test(ch) || /[\u2500-\u257F|]/.test(ch)) {
8865
9465
  if (breaks.length) breaks[breaks.length - 1] = true;
@@ -8921,18 +9521,18 @@ var HOOKS_SPEC = {
8921
9521
  return null;
8922
9522
  }
8923
9523
  };
8924
- function inspectTrust(lines2, tool, kind) {
9524
+ function inspectTrust(lines3, tool, kind) {
8925
9525
  if (kind !== "hooks") {
8926
- const v = inspectWith(lines2, tool, SPECS[tool], "trust");
9526
+ const v = inspectWith(lines3, tool, SPECS[tool], "trust");
8927
9527
  if (v || kind === "trust") return v;
8928
9528
  }
8929
- return tool === "codex" ? inspectWith(lines2, tool, HOOKS_SPEC, "hooks") : null;
9529
+ return tool === "codex" ? inspectWith(lines3, tool, HOOKS_SPEC, "hooks") : null;
8930
9530
  }
8931
- function inspectWith(lines2, tool, spec, kind) {
9531
+ function inspectWith(lines3, tool, spec, kind) {
8932
9532
  if (!spec) return null;
8933
9533
  let start = -1;
8934
- for (let i = lines2.length - 1; i >= 0; i--) {
8935
- if (spec.title.test(lines2[i])) {
9534
+ for (let i = lines3.length - 1; i >= 0; i--) {
9535
+ if (spec.title.test(lines3[i])) {
8936
9536
  start = i;
8937
9537
  break;
8938
9538
  }
@@ -8943,8 +9543,8 @@ function inspectWith(lines2, tool, spec, kind) {
8943
9543
  let cursor = -1;
8944
9544
  let cursors = 0;
8945
9545
  let last = start;
8946
- for (let i = start + 1; i < lines2.length; i++) {
8947
- const raw = inner(lines2[i]).trim();
9546
+ for (let i = start + 1; i < lines3.length; i++) {
9547
+ const raw = inner(lines3[i]).trim();
8948
9548
  let text = raw;
8949
9549
  let here2 = false;
8950
9550
  if (text && CURSOR_CHARS.includes(text[0])) {
@@ -8961,7 +9561,7 @@ function inspectWith(lines2, tool, spec, kind) {
8961
9561
  last = i;
8962
9562
  }
8963
9563
  if (!options.some((o) => o.role === "folder") || !options.some((o) => o.role === "no")) return null;
8964
- const region = lines2.slice(top, last + 1);
9564
+ const region = lines3.slice(top, last + 1);
8965
9565
  const { body, breaks } = squashLines(region);
8966
9566
  const widened = !!spec.widened && spec.widened.test(region.join("\n"));
8967
9567
  const widenedRoot = widened ? noteRoot(region) : "";
@@ -9033,38 +9633,38 @@ function trustCwdForms(cwd, real2 = realpathSync5, home = homedir10()) {
9033
9633
  }
9034
9634
  function gitStat(p) {
9035
9635
  try {
9036
- const s2 = statSync11(p);
9636
+ const s2 = statSync12(p);
9037
9637
  return s2.isDirectory() ? "dir" : "file";
9038
9638
  } catch {
9039
9639
  return null;
9040
9640
  }
9041
9641
  }
9042
- function findRepoScope(tool, cwd, stat = gitStat, home = homedir10(), real2 = realpathSync5) {
9642
+ function findRepoScope(tool, cwd, stat2 = gitStat, home = homedir10(), real2 = realpathSync5) {
9043
9643
  if (tool !== "claude_code" && tool !== "codex" || !cwd) return null;
9044
9644
  const logical = resolve7(cwd);
9045
- const a = scanRepo(logical, stat, home);
9645
+ const a = scanRepo(logical, stat2, home);
9046
9646
  let physical = logical;
9047
9647
  try {
9048
9648
  physical = real2(logical);
9049
9649
  } catch {
9050
9650
  }
9051
9651
  if (physical === logical) return a;
9052
- const b = scanRepo(physical, stat, home);
9652
+ const b = scanRepo(physical, stat2, home);
9053
9653
  return sameScope(a, b, real2) ? a : { kind: "unknown" };
9054
9654
  }
9055
- function scanRepo(start, stat, home) {
9655
+ function scanRepo(start, stat2, home) {
9056
9656
  const h = home ? home.replace(/[\\/]+$/, "") : "";
9057
9657
  let d = start.replace(/[\\/]+$/, "") || start;
9058
9658
  let aboveHome = false;
9059
9659
  for (let i = 0; i < 64; i++) {
9060
- const g = stat(join31(d, ".git"));
9660
+ const g = stat2(join32(d, ".git"));
9061
9661
  if (g) {
9062
9662
  if (aboveHome) return { kind: "unknown" };
9063
9663
  if (g === "file") return { kind: "worktree", at: d };
9064
9664
  return i === 0 ? null : { kind: "repo", root: d };
9065
9665
  }
9066
9666
  if (h && sameDir2(d, h)) aboveHome = true;
9067
- const up = dirname17(d);
9667
+ const up = dirname19(d);
9068
9668
  if (up === d) return null;
9069
9669
  d = up;
9070
9670
  }
@@ -9143,8 +9743,8 @@ async function answerTrust(io, tool, decision, forms, kind = "trust", repoRoot)
9143
9743
  }
9144
9744
  return await waitUntil2(io, (l) => !inspectTrust(l, tool, kind)) ? "done" : "partial";
9145
9745
  }
9146
- function trustWaiting(lines2, tool, kind = "trust") {
9147
- return inspectTrust(lines2, tool, kind) !== null;
9746
+ function trustWaiting(lines3, tool, kind = "trust") {
9747
+ return inspectTrust(lines3, tool, kind) !== null;
9148
9748
  }
9149
9749
  var TRUST_STABLE_MS = 500;
9150
9750
  var TRUST_RENOTIFY_MS = 3e4;
@@ -9190,8 +9790,8 @@ var TrustWatcher = class {
9190
9790
  this.#mismatchLogged = true;
9191
9791
  this.#d.log(msg);
9192
9792
  }
9193
- tick(lines2) {
9194
- const v = lines2 ? inspectTrust(lines2, this.#d.tool) : null;
9793
+ tick(lines3) {
9794
+ const v = lines3 ? inspectTrust(lines3, this.#d.tool) : null;
9195
9795
  if (!v || v.kind !== this.#kind) {
9196
9796
  this.#since = null;
9197
9797
  this.#reported = false;
@@ -9257,7 +9857,7 @@ var TrustWatcher = class {
9257
9857
  };
9258
9858
 
9259
9859
  // src/wrap/trustNotify.ts
9260
- import { readFileSync as readFileSync24 } from "fs";
9860
+ import { readFileSync as readFileSync25 } from "fs";
9261
9861
  import { request } from "http";
9262
9862
  var TRUST_EVENT = "DeskBuddyTrustPrompt";
9263
9863
  var LOGIN_EVENT = "DeskBuddyLoginRequired";
@@ -9285,7 +9885,7 @@ function postNotify(body, confPath) {
9285
9885
  };
9286
9886
  let conf;
9287
9887
  try {
9288
- conf = JSON.parse(readFileSync24(confPath, "utf8"));
9888
+ conf = JSON.parse(readFileSync25(confPath, "utf8"));
9289
9889
  } catch {
9290
9890
  return finish(false);
9291
9891
  }
@@ -9369,7 +9969,7 @@ var TrustBridge = class {
9369
9969
  return null;
9370
9970
  }
9371
9971
  if (!rec || rec.trust !== true || rec.launched !== true || rec.tool !== tool || !rec.cwd) return null;
9372
- return (this.#d.alive ?? pidAlive3)(rec.pid) ? rec : null;
9972
+ return (this.#d.alive ?? pidAlive4)(rec.pid) ? rec : null;
9373
9973
  }
9374
9974
  onNotify(req2) {
9375
9975
  void this.#onNotify(req2).catch((e) => this.#d.log(`\u4FE1\u4EFB\u6846\u63A8\u5361\u51FA\u9519\uFF1A${e?.message ?? e}`));
@@ -9508,7 +10108,7 @@ var TrustBridge = class {
9508
10108
  }
9509
10109
  }
9510
10110
  };
9511
- function pidAlive3(pid) {
10111
+ function pidAlive4(pid) {
9512
10112
  if (!Number.isInteger(pid) || pid <= 0) return false;
9513
10113
  try {
9514
10114
  process.kill(pid, 0);
@@ -9592,7 +10192,7 @@ var LoginRequired = class {
9592
10192
  rec = null;
9593
10193
  }
9594
10194
  if (!rec || rec.tool !== "workbuddy" || rec.answer !== true || rec.launched !== true) return;
9595
- if (!(this.#d.alive ?? pidAlive4)(rec.pid)) return;
10195
+ if (!(this.#d.alive ?? pidAlive5)(rec.pid)) return;
9596
10196
  this.#done.add(windowId);
9597
10197
  const short = windowId.slice(0, 8);
9598
10198
  const sid = this.#d.sessionOfWindow(windowId);
@@ -9617,7 +10217,7 @@ var LoginRequired = class {
9617
10217
  this.#d.report(entry.cmd, entry.sid || sid, status, LOGIN_REASON);
9618
10218
  }
9619
10219
  };
9620
- function pidAlive4(pid) {
10220
+ function pidAlive5(pid) {
9621
10221
  if (!Number.isInteger(pid) || pid <= 0) return false;
9622
10222
  try {
9623
10223
  process.kill(pid, 0);
@@ -9628,10 +10228,10 @@ function pidAlive4(pid) {
9628
10228
  }
9629
10229
 
9630
10230
  // src/daemon/dumateWatch.ts
9631
- import { execFileSync as execFileSync14, spawn as spawn4 } from "child_process";
9632
- import { readdirSync as readdirSync8, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
10231
+ import { execFileSync as execFileSync14, spawn as spawn5 } from "child_process";
10232
+ import { readdirSync as readdirSync8, realpathSync as realpathSync6, statSync as statSync13 } from "fs";
9633
10233
  import { request as httpRequest } from "http";
9634
- import { join as join32 } from "path";
10234
+ import { join as join33 } from "path";
9635
10235
 
9636
10236
  // hook/ocTranslate.mjs
9637
10237
  function toCcTool(tool, args) {
@@ -9857,7 +10457,7 @@ function scanSessionDirs(root, now, maxAgeMs = ACTIVE_DIR_MS) {
9857
10457
  const out = [];
9858
10458
  const list = (p) => {
9859
10459
  try {
9860
- return readdirSync8(p, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join32(p, d.name));
10460
+ return readdirSync8(p, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join33(p, d.name));
9861
10461
  } catch {
9862
10462
  return [];
9863
10463
  }
@@ -9866,7 +10466,7 @@ function scanSessionDirs(root, now, maxAgeMs = ACTIVE_DIR_MS) {
9866
10466
  for (const day of list(u).sort().reverse().slice(0, 2))
9867
10467
  for (const dir of list(day)) {
9868
10468
  try {
9869
- const at = statSync12(dir).mtimeMs;
10469
+ const at = statSync13(dir).mtimeMs;
9870
10470
  if (now - at <= maxAgeMs) out.push({ dir, at });
9871
10471
  } catch {
9872
10472
  }
@@ -10316,7 +10916,7 @@ var DumateWatch = class {
10316
10916
  const now = this.#now();
10317
10917
  if (now - this.#scanAt >= SCAN_DIRS_MS) {
10318
10918
  this.#scanAt = now;
10319
- for (const d of (this.#d.sessionDirs ?? (() => scanSessionDirs(join32(qianfanHome(), "workspace", "sessions"), now)))()) {
10919
+ for (const d of (this.#d.sessionDirs ?? (() => scanSessionDirs(join33(qianfanHome(), "workspace", "sessions"), now)))()) {
10320
10920
  if (!this.#dirs.has(dirKey(d))) this.#touchDir(d, now);
10321
10921
  }
10322
10922
  }
@@ -10387,7 +10987,7 @@ function runHook(node, input, agentPid) {
10387
10987
  return new Promise((resolve10) => {
10388
10988
  let child;
10389
10989
  try {
10390
- child = spawn4(node, [installedHookPath(), "dumate"], {
10990
+ child = spawn5(node, [installedHookPath(), "dumate"], {
10391
10991
  stdio: ["pipe", "ignore", "ignore"],
10392
10992
  env: { ...process.env, DESKBUDDY_AGENT_PID: String(agentPid) }
10393
10993
  });
@@ -10411,8 +11011,376 @@ function runHook(node, input, agentPid) {
10411
11011
  });
10412
11012
  }
10413
11013
 
11014
+ // src/daemon/workbuddyAppDb.ts
11015
+ import { execFile as execFile8 } from "child_process";
11016
+ import { closeSync as closeSync8, existsSync as existsSync22, mkdirSync as mkdirSync25, openSync as openSync8, readdirSync as readdirSync9, readFileSync as readFileSync27, readSync as readSync7, renameSync as renameSync16, rmSync as rmSync15, statSync as statSync15, writeFileSync as writeFileSync23 } from "fs";
11017
+ import { createRequire as createRequire4 } from "module";
11018
+ import { basename as basename10, dirname as dirname21, isAbsolute as isAbsolute3, join as join35 } from "path";
11019
+
11020
+ // src/core/title.ts
11021
+ import { closeSync as closeSync7, existsSync as existsSync21, openSync as openSync7, readFileSync as readFileSync26, readSync as readSync6, statSync as statSync14 } from "fs";
11022
+ import { createRequire as createRequire3 } from "module";
11023
+ import { homedir as homedir11 } from "os";
11024
+ import { basename as basename9, dirname as dirname20, isAbsolute as isAbsolute2, join as join34 } from "path";
11025
+ var TRANSCRIPT_TAIL_BYTES = 64 * 1024;
11026
+ var SESSION_TITLE_MAX = 100;
11027
+ function cleanTitle(text) {
11028
+ if (typeof text !== "string") return "";
11029
+ const flat = text.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, " ").replace(/\s+/g, " ").trim();
11030
+ return Array.from(flat).slice(0, SESSION_TITLE_MAX).join("").trimEnd();
11031
+ }
11032
+ var requireHere = createRequire3(import.meta.url);
11033
+ var QODER_DATA_DIRS = { qoder_cn: "com.qodercn.app.stable", qoder: "com.qoder.app.stable" };
11034
+ var QODER_APP_DIR_ENV = { qoder_cn: "DESKBUDDY_QODER_CN_APP_DIR", qoder: "DESKBUDDY_QODER_APP_DIR" };
11035
+ function qoderDbPaths(tool, platform = process.platform, home = homedir11(), env = process.env) {
11036
+ let root;
11037
+ if (platform === "darwin") root = join34(home, "Library", "Application Support");
11038
+ else if (platform === "win32") root = env.APPDATA || join34(home, "AppData", "Roaming");
11039
+ else root = env.XDG_CONFIG_HOME || join34(home, ".config");
11040
+ const one = tool ? QODER_DATA_DIRS[tool] : void 0;
11041
+ const tools = one ? [tool] : ["qoder_cn", "qoder"];
11042
+ return tools.map((t) => join34(env[QODER_APP_DIR_ENV[t] ?? ""] || join34(root, QODER_DATA_DIRS[t] ?? ""), "main.sqlite"));
11043
+ }
11044
+
11045
+ // src/daemon/workbuddyAppDb.ts
11046
+ var WB_SESSION_COLUMNS = [
11047
+ "id",
11048
+ "cwd",
11049
+ "user_id",
11050
+ "title",
11051
+ "custom_title",
11052
+ "status",
11053
+ "created_at",
11054
+ "updated_at",
11055
+ "deleted_at",
11056
+ "is_playground",
11057
+ "model",
11058
+ "last_activity_at",
11059
+ "source_mode",
11060
+ "mode",
11061
+ "permission_mode",
11062
+ "transport",
11063
+ "unread",
11064
+ "addon_selection"
11065
+ ];
11066
+ var WB_DB_SCRIPT = String.raw`
11067
+ const { DatabaseSync } = require('node:sqlite');
11068
+ const db = new DatabaseSync(input.db);
11069
+ try {
11070
+ db.exec('PRAGMA busy_timeout = 1000');
11071
+ const info = db.prepare('PRAGMA table_info(sessions)').all();
11072
+ const have = new Set(info.map((r) => r.name));
11073
+ const miss = cols.filter((c) => !have.has(c));
11074
+ if (miss.length) return { status: 'schema', detail: miss.join(',') };
11075
+ const pk = info.filter((r) => Number(r.pk) > 0).map((r) => r.name);
11076
+ if (pk.length !== 1 || pk[0] !== 'id') return { status: 'schema', detail: 'pk' };
11077
+ db.exec('BEGIN IMMEDIATE');
11078
+ try {
11079
+ if (db.prepare('select 1 as x from sessions where id = ?').get(input.sessionId)) {
11080
+ db.exec('ROLLBACK');
11081
+ return { status: 'exists' };
11082
+ }
11083
+ const u = db.prepare("select user_id from sessions where transport = 'local' and deleted_at is null and user_id <> '' order by coalesce(last_activity_at, updated_at, created_at) desc limit 1").get();
11084
+ if (!u || typeof u.user_id !== 'string' || !u.user_id) {
11085
+ db.exec('ROLLBACK');
11086
+ return { status: 'no_user' };
11087
+ }
11088
+ const addon = JSON.stringify({ version: 1, welcomeMode: 'working', interactionMode: 'craft', permissionMode: 'default' });
11089
+ const r = db.prepare("insert into sessions(id,cwd,user_id,title,custom_title,status,created_at,updated_at,deleted_at,is_playground,model,last_activity_at,source_mode,mode,permission_mode,transport,unread,addon_selection) values(?,?,?,?,NULL,'completed',?,?,NULL,0,'auto',?,'working','craft','default','local',1,?) on conflict(id) do nothing")
11090
+ .run(input.sessionId, input.cwd, u.user_id, input.title, input.createdAt, input.updatedAt, input.updatedAt, addon);
11091
+ db.exec('COMMIT');
11092
+ return { status: Number(r.changes) > 0 ? 'inserted' : 'exists' };
11093
+ } catch (e) {
11094
+ try { db.exec('ROLLBACK'); } catch (_) {}
11095
+ throw e;
11096
+ }
11097
+ } finally {
11098
+ db.close();
11099
+ }
11100
+ `;
11101
+ function wbElectronScript() {
11102
+ return `let out;try{const input=JSON.parse(require('fs').readFileSync(0,'utf8'));out=(function(require,input,cols){${WB_DB_SCRIPT}})(require,input,${JSON.stringify(WB_SESSION_COLUMNS)})}catch(e){out={status:'error',detail:String(e&&e.message||e).slice(0,200)}}process.stdout.write(JSON.stringify(out))`;
11103
+ }
11104
+ function findWbTranscript(configDir, sessionId) {
11105
+ if (!SESSION_UUID_RE.test(sessionId)) return null;
11106
+ let dirs;
11107
+ try {
11108
+ dirs = readdirSync9(join35(configDir, "projects"));
11109
+ } catch {
11110
+ return null;
11111
+ }
11112
+ for (const d of dirs) {
11113
+ const p = join35(configDir, "projects", d, `${sessionId}.jsonl`);
11114
+ if (existsSync22(p)) return p;
11115
+ }
11116
+ return null;
11117
+ }
11118
+ var SLICE = 64 * 1024;
11119
+ function readSlice(p, from) {
11120
+ const fd = openSync8(p, "r");
11121
+ try {
11122
+ const size = statSync15(p).size;
11123
+ const len = Math.min(size, SLICE);
11124
+ const buf = Buffer.alloc(len);
11125
+ readSync7(fd, buf, 0, len, from === "head" ? 0 : size - len);
11126
+ return buf.toString("utf8");
11127
+ } finally {
11128
+ closeSync8(fd);
11129
+ }
11130
+ }
11131
+ function tsOf(v) {
11132
+ if (typeof v === "number" && Number.isFinite(v) && v > 1e12) return Math.floor(v);
11133
+ if (typeof v === "string") {
11134
+ const n = Date.parse(v);
11135
+ if (Number.isFinite(n)) return n;
11136
+ }
11137
+ return null;
11138
+ }
11139
+ function jsonLines(text) {
11140
+ const out = [];
11141
+ for (const l of text.split("\n")) {
11142
+ try {
11143
+ const o = JSON.parse(l);
11144
+ if (o && typeof o === "object" && !Array.isArray(o)) out.push(o);
11145
+ } catch {
11146
+ }
11147
+ }
11148
+ return out;
11149
+ }
11150
+ function userText2(o) {
11151
+ let c;
11152
+ if (o.type === "user") c = o.message?.content;
11153
+ else if (o.type === "message" && o.role === "user") c = o.content;
11154
+ else return "";
11155
+ if (typeof c === "string") return c;
11156
+ if (Array.isArray(c)) {
11157
+ for (const b of c) {
11158
+ if ((b?.type === "text" || b?.type === "input_text") && typeof b.text === "string" && !b.text.startsWith("<system-reminder")) return b.text;
11159
+ }
11160
+ }
11161
+ return "";
11162
+ }
11163
+ function wbTranscriptInfo(p) {
11164
+ const head = jsonLines(readSlice(p, "head"));
11165
+ const tail2 = jsonLines(readSlice(p, "tail"));
11166
+ const mtime = Math.floor(statSync15(p).mtimeMs);
11167
+ const hs = head.map((o) => tsOf(o.timestamp)).filter((n) => n !== null);
11168
+ const ts = tail2.map((o) => tsOf(o.timestamp)).filter((n) => n !== null);
11169
+ const createdAt = hs.length ? Math.min(...hs) : mtime;
11170
+ const updatedAt = Math.max(createdAt, ts.length ? Math.max(...ts) : mtime);
11171
+ let title = "";
11172
+ for (const o of head) {
11173
+ title = cleanTitle(userText2(o));
11174
+ if (title) break;
11175
+ }
11176
+ return { createdAt, updatedAt, title };
11177
+ }
11178
+ var electronBroken = false;
11179
+ function markElectronBroken(v = true) {
11180
+ electronBroken = v;
11181
+ }
11182
+ var requireHere2 = createRequire4(import.meta.url);
11183
+ function loadNodeSqlite() {
11184
+ const orig = process.emitWarning;
11185
+ process.emitWarning = function(...args) {
11186
+ if (String(args[0] ?? "").includes("SQLite")) return;
11187
+ return orig.apply(process, args);
11188
+ };
11189
+ try {
11190
+ return requireHere2("node:sqlite");
11191
+ } finally {
11192
+ process.emitWarning = orig;
11193
+ }
11194
+ }
11195
+ function wbElectronCandidates(appCliPath) {
11196
+ const m = /^(.*?)[\\/]resources[\\/]app\.asar\.unpacked[\\/]/i.exec(appCliPath);
11197
+ if (!m) return [];
11198
+ const root = m[1];
11199
+ if (/\.app[\\/]Contents$/.test(root)) return [join35(root, "MacOS", "Electron"), join35(root, "MacOS", "WorkBuddy")];
11200
+ return [join35(root, "WorkBuddy.exe")];
11201
+ }
11202
+ function defaultWbElectron(d = defaultLocateDeps()) {
11203
+ if ((d.platform ?? process.platform) === "win32") return null;
11204
+ if (electronBroken) return null;
11205
+ try {
11206
+ const app = locateWorkbuddyApp(d);
11207
+ if (!app) return null;
11208
+ const cli = [app.file, ...app.prefixArgs].find((p) => /app\.asar\.unpacked/i.test(p));
11209
+ if (!cli) return null;
11210
+ return wbElectronCandidates(cli).find((p) => d.isExecutable(p) || d.exists(p)) ?? null;
11211
+ } catch {
11212
+ return null;
11213
+ }
11214
+ }
11215
+ function defaultAppRunning(platform = process.platform) {
11216
+ return new Promise((resolve10) => {
11217
+ try {
11218
+ if (platform === "darwin") {
11219
+ execFile8("pgrep", ["-f", "WorkBuddy.app/Contents/MacOS/"], { timeout: 5e3 }, (err) => {
11220
+ const code = err?.code;
11221
+ resolve10(!err ? true : code === 1 ? false : true);
11222
+ });
11223
+ return;
11224
+ }
11225
+ if (platform === "win32") {
11226
+ execFile8(
11227
+ "tasklist",
11228
+ ["/FI", "IMAGENAME eq WorkBuddy.exe", "/FO", "CSV", "/NH"],
11229
+ { timeout: 1e4, windowsHide: true, encoding: "utf8" },
11230
+ (err, stdout7) => resolve10(err ? true : /"WorkBuddy\.exe"/i.test(String(stdout7)))
11231
+ );
11232
+ return;
11233
+ }
11234
+ resolve10(true);
11235
+ } catch {
11236
+ resolve10(true);
11237
+ }
11238
+ });
11239
+ }
11240
+ var PENDING_MAX = 20;
11241
+ var PENDING_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
11242
+ function pendingPath() {
11243
+ return join35(deskbuddyHome(), "workbuddy-pending.json");
11244
+ }
11245
+ function loadPendingFile() {
11246
+ try {
11247
+ const v = JSON.parse(readFileSync27(pendingPath(), "utf8"));
11248
+ return Array.isArray(v) ? v.filter((j) => j && typeof j.sessionId === "string" && typeof j.cwd === "string") : [];
11249
+ } catch {
11250
+ return [];
11251
+ }
11252
+ }
11253
+ function savePendingFile(jobs) {
11254
+ try {
11255
+ if (jobs.length === 0) {
11256
+ rmSync15(pendingPath(), { force: true });
11257
+ return;
11258
+ }
11259
+ mkdirSync25(deskbuddyHome(), { recursive: true });
11260
+ const tmp = pendingPath() + ".tmp";
11261
+ writeFileSync23(tmp, JSON.stringify(jobs), { mode: 384 });
11262
+ renameSync16(tmp, pendingPath());
11263
+ } catch {
11264
+ }
11265
+ }
11266
+ var defaultWbAppDbDeps = () => ({
11267
+ dbPath: () => workbuddyDbPath(),
11268
+ configDir: () => dirname21(workbuddySettingsPath()),
11269
+ loadSqlite: loadNodeSqlite,
11270
+ electron: () => defaultWbElectron(),
11271
+ runElectron: (bin, script, input) => new Promise((resolve10, reject) => {
11272
+ const env = { ...process.env, ELECTRON_RUN_AS_NODE: "1" };
11273
+ const c = execFile8(
11274
+ bin,
11275
+ ["-e", script],
11276
+ { env, timeout: 15e3, windowsHide: true, maxBuffer: 1024 * 1024 },
11277
+ (err, stdout7) => err ? reject(err) : resolve10(String(stdout7))
11278
+ );
11279
+ c.stdin?.on("error", () => {
11280
+ });
11281
+ c.stdin?.end(input);
11282
+ }),
11283
+ appRunning: () => defaultAppRunning(),
11284
+ loadPending: loadPendingFile,
11285
+ savePending: savePendingFile,
11286
+ onElectronBroken: () => markElectronBroken()
11287
+ });
11288
+ async function runDb(input, d) {
11289
+ let mod = null;
11290
+ try {
11291
+ mod = d.loadSqlite();
11292
+ } catch {
11293
+ mod = null;
11294
+ }
11295
+ if (mod) {
11296
+ const fn = new Function("require", "input", "cols", WB_DB_SCRIPT);
11297
+ try {
11298
+ return fn((id) => id === "node:sqlite" ? mod : requireHere2(id), input, WB_SESSION_COLUMNS);
11299
+ } catch (e) {
11300
+ return { status: "error", detail: String(e?.message ?? e).slice(0, 200) };
11301
+ }
11302
+ }
11303
+ const bin = d.electron();
11304
+ if (!bin) return "no_sqlite";
11305
+ let raw;
11306
+ try {
11307
+ raw = await d.runElectron(bin, wbElectronScript(), JSON.stringify(input));
11308
+ } catch (e) {
11309
+ return { status: "error", detail: `electron: ${String(e?.message ?? e).slice(0, 160)}` };
11310
+ }
11311
+ let out = null;
11312
+ try {
11313
+ out = JSON.parse(raw);
11314
+ } catch {
11315
+ out = null;
11316
+ }
11317
+ if (out && typeof out.status === "string") return out;
11318
+ try {
11319
+ d.onElectronBroken?.();
11320
+ } catch {
11321
+ }
11322
+ return { status: "error", detail: "electron: bad output\uFF0C\u4E4B\u540E\u4E0D\u518D\u8D70\u8FD9\u6761\u8DEF" };
11323
+ }
11324
+ async function registerWorkbuddySession(job, d = defaultWbAppDbDeps(), now = Date.now()) {
11325
+ try {
11326
+ if (!SESSION_UUID_RE.test(job.sessionId)) return "skip bad_session";
11327
+ if (await d.appRunning()) {
11328
+ const list = d.loadPending().filter((j) => j.sessionId !== job.sessionId);
11329
+ list.push({ sessionId: job.sessionId, cwd: job.cwd, fallbackTitle: job.fallbackTitle, at: now });
11330
+ d.savePending(list.slice(-PENDING_MAX));
11331
+ return "deferred app_running\uFF08WorkBuddy \u5F00\u7740\uFF0C\u5173\u6389\u540E\u518D\u767B\u8BB0\uFF09";
11332
+ }
11333
+ const flushed = await flushWithAppClosed(d, now, job.sessionId);
11334
+ const r = await registerNow(job, d);
11335
+ return flushed ? `${r}\uFF08\u53E6\u8865\u767B\u8BB0 ${flushed} \u6761\uFF09` : r;
11336
+ } catch (e) {
11337
+ return `error ${String(e?.message ?? e).slice(0, 160)}`;
11338
+ }
11339
+ }
11340
+ async function flushPendingWorkbuddy(d = defaultWbAppDbDeps(), now = Date.now()) {
11341
+ try {
11342
+ if (d.loadPending().length === 0) return "";
11343
+ if (await d.appRunning()) return "";
11344
+ const n = await flushWithAppClosed(d, now);
11345
+ return n ? `\u8865\u767B\u8BB0 ${n} \u6761` : "";
11346
+ } catch (e) {
11347
+ return `error ${String(e?.message ?? e).slice(0, 160)}`;
11348
+ }
11349
+ }
11350
+ async function flushWithAppClosed(d, now, except) {
11351
+ const list = d.loadPending();
11352
+ if (list.length === 0) return 0;
11353
+ let n = 0;
11354
+ for (const j of list) {
11355
+ if (j.sessionId === except || j.at !== void 0 && now - j.at > PENDING_TTL_MS) continue;
11356
+ const r = await registerNow(j, d);
11357
+ if (r === "inserted") n++;
11358
+ }
11359
+ d.savePending([]);
11360
+ return n;
11361
+ }
11362
+ async function registerNow(job, d) {
11363
+ try {
11364
+ if (!SESSION_UUID_RE.test(job.sessionId)) return "skip bad_session";
11365
+ if (!job.cwd || !isAbsolute3(job.cwd)) return "skip bad_cwd";
11366
+ const db = d.dbPath();
11367
+ if (!db || !existsSync22(db)) return "skip no_db";
11368
+ const tp = findWbTranscript(d.configDir(), job.sessionId);
11369
+ if (!tp) return "skip no_transcript";
11370
+ const info = wbTranscriptInfo(tp);
11371
+ const title = info.title || cleanTitle(job.fallbackTitle ?? "") || basename10(job.cwd) || job.cwd;
11372
+ const r = await runDb({ db, sessionId: job.sessionId, cwd: job.cwd, title, createdAt: info.createdAt, updatedAt: info.updatedAt }, d);
11373
+ if (r === "no_sqlite") return "skip no_sqlite";
11374
+ if (r.status === "schema") return `skip schema ${r.detail ?? ""}`.trim();
11375
+ if (r.status === "no_user") return "skip no_user";
11376
+ return [r.status, r.detail ?? ""].filter(Boolean).join(" ");
11377
+ } catch (e) {
11378
+ return `error ${String(e?.message ?? e).slice(0, 160)}`;
11379
+ }
11380
+ }
11381
+
10414
11382
  // src/daemon/codexWatch.ts
10415
- import { execFileSync as execFileSync15, spawn as spawn5 } from "child_process";
11383
+ import { execFileSync as execFileSync15, spawn as spawn6 } from "child_process";
10416
11384
  import WebSocket from "ws";
10417
11385
  var TURN_LIMIT_MS = 10 * 60 * 1e3;
10418
11386
  var QUEUE_MAX_MS = 10 * 60 * 1e3;
@@ -10499,6 +11467,8 @@ var CodexWatch = class {
10499
11467
  #idle = /* @__PURE__ */ new Set();
10500
11468
  /** 连接代数:断线重连后,上一条连接遗留的订阅重试不再跑 */
10501
11469
  #gen = 0;
11470
+ /** 这条连接上 thread/resume 不能带 excludeTurns(app-server 要求 experimentalApi,见 #resume) */
11471
+ #noExcludeTurns = false;
10502
11472
  /** initialize 已成功 */
10503
11473
  #ready = false;
10504
11474
  /** app-server 里已加载的会话(thread/loaded/list + 通知维护)。窗口开着 = 能往里发,据此上报 remoteWindow */
@@ -10565,6 +11535,7 @@ var CodexWatch = class {
10565
11535
  this.#ws = ws;
10566
11536
  this.#url = url;
10567
11537
  this.#gen++;
11538
+ this.#noExcludeTurns = false;
10568
11539
  ws.on("open", () => void this.#onOpen());
10569
11540
  ws.on("message", (buf) => this.#onMessage(String(buf)));
10570
11541
  ws.on("close", (code) => this.#teardown(`\u5173\u95ED ${code}`));
@@ -10623,8 +11594,21 @@ var CodexWatch = class {
10623
11594
  this.#send({ id, method, params });
10624
11595
  });
10625
11596
  }
11597
+ /**
11598
+ * thread/resume。excludeTurns(不回历史轮次,省流量)是实验性字段:app-server 回 -32600 且提到 experimentalApi 时,
11599
+ * 去掉它重发,并且这条连接之后都不再带。不带的代价只是回话里多了历史轮次;#applyThread 只看最后一轮是否进行中,没有副作用
11600
+ */
11601
+ async #resume(threadId, excludeTurns) {
11602
+ const gen = this.#gen;
11603
+ if (!excludeTurns || this.#noExcludeTurns) return this.#request("thread/resume", { threadId });
11604
+ const r = await this.#request("thread/resume", { threadId, excludeTurns: true });
11605
+ if (!needsExperimental(r) || gen !== this.#gen) return r;
11606
+ this.#noExcludeTurns = true;
11607
+ this.#d.log("Codex app-server \u4E0D\u8BA4 excludeTurns\uFF08\u8981 experimentalApi\uFF09\uFF0C\u53BB\u6389\u540E\u91CD\u65B0\u8BA2\u9605");
11608
+ return this.#request("thread/resume", { threadId });
11609
+ }
10626
11610
  async #onOpen() {
10627
- const r = await this.#request("initialize", { clientInfo: { name: "deskbuddy", version: "1" } });
11611
+ const r = await this.#request("initialize", { clientInfo: { name: "deskbuddy", version: "1" }, capabilities: { experimentalApi: true } });
10628
11612
  if (r.error) {
10629
11613
  this.#d.log(`Codex app-server \u521D\u59CB\u5316\u5931\u8D25\uFF1A${JSON.stringify(r.error).slice(0, 120)}`);
10630
11614
  this.#teardown();
@@ -10748,7 +11732,7 @@ var CodexWatch = class {
10748
11732
  const gen = this.#gen;
10749
11733
  const attempt = async () => {
10750
11734
  if (!this.#ws || gen !== this.#gen || !this.#retrying.has(threadId)) return;
10751
- const req2 = this.#request("thread/resume", { threadId, excludeTurns: !this.#waiting.has(threadId) });
11735
+ const req2 = this.#resume(threadId, !this.#waiting.has(threadId));
10752
11736
  this.#inflight.set(threadId, req2);
10753
11737
  const r = await req2;
10754
11738
  if (this.#inflight.get(threadId) === req2) this.#inflight.delete(threadId);
@@ -10853,7 +11837,7 @@ var CodexWatch = class {
10853
11837
  }
10854
11838
  if (!this.#subscribed.has(tid)) {
10855
11839
  this.#retrying.delete(tid);
10856
- const req3 = this.#request("thread/resume", { threadId: tid, excludeTurns: !this.#waiting.has(tid) });
11840
+ const req3 = this.#resume(tid, !this.#waiting.has(tid));
10857
11841
  this.#inflight.set(tid, req3);
10858
11842
  const r2 = await req3;
10859
11843
  if (this.#inflight.get(tid) === req3) this.#inflight.delete(tid);
@@ -11244,6 +12228,11 @@ var CodexWatch = class {
11244
12228
  }
11245
12229
  };
11246
12230
  var NO_FILE_LIST = "\u53D6\u4E0D\u5230\u6539\u52A8\u7684\u6587\u4EF6";
12231
+ function needsExperimental(r) {
12232
+ const e = r.error;
12233
+ if (!e || r.local) return false;
12234
+ return e.code === -32600 && typeof e.message === "string" && /experimentalApi/.test(e.message);
12235
+ }
11247
12236
  var FEEDBACK_EXPIRE_MS = 2 * 60 * 1e3;
11248
12237
  function nativeReply(reply, field) {
11249
12238
  const n = reply.native;
@@ -11371,7 +12360,7 @@ function runCodexHook(node, input) {
11371
12360
  if (!pid) return;
11372
12361
  let child;
11373
12362
  try {
11374
- child = spawn5(node, [installedHookPath(), "codex"], {
12363
+ child = spawn6(node, [installedHookPath(), "codex"], {
11375
12364
  stdio: ["pipe", "ignore", "ignore"],
11376
12365
  env: { ...process.env, DESKBUDDY_AGENT_PID: String(pid) }
11377
12366
  });
@@ -11410,23 +12399,23 @@ function codexQuestionInput(p) {
11410
12399
  }
11411
12400
 
11412
12401
  // src/core/codexRemote.ts
11413
- import { mkdirSync as mkdirSync24, renameSync as renameSync16, rmSync as rmSync14, writeFileSync as writeFileSync22 } from "fs";
11414
- import { join as join33 } from "path";
12402
+ import { mkdirSync as mkdirSync26, renameSync as renameSync17, rmSync as rmSync16, writeFileSync as writeFileSync24 } from "fs";
12403
+ import { join as join36 } from "path";
11415
12404
  function codexRemotePath() {
11416
- return join33(deskbuddyHome(), "codex-remote.json");
12405
+ return join36(deskbuddyHome(), "codex-remote.json");
11417
12406
  }
11418
12407
  function writeCodexRemote(threads, pid = process.pid, now = Date.now()) {
11419
12408
  try {
11420
- mkdirSync24(deskbuddyHome(), { recursive: true });
12409
+ mkdirSync26(deskbuddyHome(), { recursive: true });
11421
12410
  const p = codexRemotePath();
11422
- writeFileSync22(p + ".tmp", JSON.stringify({ pid, threads: [...threads], at: now }), "utf8");
11423
- renameSync16(p + ".tmp", p);
12411
+ writeFileSync24(p + ".tmp", JSON.stringify({ pid, threads: [...threads], at: now }), "utf8");
12412
+ renameSync17(p + ".tmp", p);
11424
12413
  } catch {
11425
12414
  }
11426
12415
  }
11427
12416
  function removeCodexRemote() {
11428
12417
  try {
11429
- rmSync14(codexRemotePath(), { force: true });
12418
+ rmSync16(codexRemotePath(), { force: true });
11430
12419
  } catch {
11431
12420
  }
11432
12421
  }
@@ -11457,19 +12446,19 @@ function isTerminalReport(s2) {
11457
12446
  return rest === "";
11458
12447
  }
11459
12448
  var PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
11460
- function inspectLines(lines2, undimmed) {
11461
- const text = lines2.join("\n");
12449
+ function inspectLines(lines3, undimmed) {
12450
+ const text = lines3.join("\n");
11462
12451
  const trust = TRUST_RE.test(text);
11463
12452
  const permission = PERMISSION_RE.test(text);
11464
12453
  const rules = [];
11465
- lines2.forEach((l, i) => {
12454
+ lines3.forEach((l, i) => {
11466
12455
  if (/^─{20,}/.test(l.trim())) rules.push(i);
11467
12456
  });
11468
12457
  let input = null;
11469
12458
  if (rules.length >= 2) {
11470
12459
  const a = rules[rules.length - 2];
11471
12460
  const b = rules[rules.length - 1];
11472
- const src = undimmed && undimmed.length === lines2.length ? undimmed : lines2;
12461
+ const src = undimmed && undimmed.length === lines3.length ? undimmed : lines3;
11473
12462
  input = src.slice(a + 1, b).map((l) => l.trimEnd()).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
11474
12463
  }
11475
12464
  const v = { permission, input };
@@ -11493,18 +12482,18 @@ function windowForSession(sessionId) {
11493
12482
  }
11494
12483
 
11495
12484
  // src/core/interrupt.ts
11496
- import { closeSync as closeSync7, openSync as openSync7, readdirSync as readdirSync9, readSync as readSync6, statSync as statSync13 } from "fs";
11497
- import { dirname as dirname18, join as join34 } from "path";
12485
+ import { closeSync as closeSync9, openSync as openSync9, readdirSync as readdirSync10, readSync as readSync8, statSync as statSync16 } from "fs";
12486
+ import { dirname as dirname22, join as join37 } from "path";
11498
12487
  var INTERRUPT_TOOLS = ["claude_code", "qoder_cn", "qoder"];
11499
12488
  function transcriptHome(tool) {
11500
12489
  if (tool === "claude_code") return claudeHome();
11501
- if (tool === "qoder_cn") return dirname18(qoderCnSettingsPath());
11502
- if (tool === "qoder") return dirname18(qoderSettingsPath());
12490
+ if (tool === "qoder_cn") return dirname22(qoderCnSettingsPath());
12491
+ if (tool === "qoder") return dirname22(qoderSettingsPath());
11503
12492
  return null;
11504
12493
  }
11505
12494
  var TAIL = 64 * 1024;
11506
12495
  var SID_RE2 = /^[A-Za-z0-9_-]{1,64}$/;
11507
- function userText2(content) {
12496
+ function userText3(content) {
11508
12497
  if (typeof content === "string") return content;
11509
12498
  if (!Array.isArray(content)) return "";
11510
12499
  return content.filter((x) => x && x.type === "text").map((x) => String(x.text ?? "")).join("\n");
@@ -11512,14 +12501,14 @@ function userText2(content) {
11512
12501
  function interruptedAt(file) {
11513
12502
  let fd;
11514
12503
  try {
11515
- const st = statSync13(file);
12504
+ const st = statSync16(file);
11516
12505
  const len = Math.min(st.size, TAIL);
11517
12506
  const buf = Buffer.alloc(len);
11518
- fd = openSync7(file, "r");
11519
- readSync6(fd, buf, 0, len, st.size - len);
11520
- const lines2 = buf.toString("utf8").split("\n");
11521
- for (let i = lines2.length - 1; i >= 0; i--) {
11522
- const line = lines2[i];
12507
+ fd = openSync9(file, "r");
12508
+ readSync8(fd, buf, 0, len, st.size - len);
12509
+ const lines3 = buf.toString("utf8").split("\n");
12510
+ for (let i = lines3.length - 1; i >= 0; i--) {
12511
+ const line = lines3[i];
11523
12512
  if (line.indexOf('"assistant"') === -1 && line.indexOf('"user"') === -1) continue;
11524
12513
  let o;
11525
12514
  try {
@@ -11530,7 +12519,7 @@ function interruptedAt(file) {
11530
12519
  if (!o || !o.message || o.isSidechain === true) continue;
11531
12520
  if (o.type === "assistant") return null;
11532
12521
  if (o.type !== "user") continue;
11533
- const text = userText2(o.message.content).trim();
12522
+ const text = userText3(o.message.content).trim();
11534
12523
  if (!text) return null;
11535
12524
  if (INTERRUPT_RE.test(text)) {
11536
12525
  const at = typeof o.timestamp === "string" ? Date.parse(o.timestamp) : NaN;
@@ -11545,7 +12534,7 @@ function interruptedAt(file) {
11545
12534
  } finally {
11546
12535
  if (fd !== void 0) {
11547
12536
  try {
11548
- closeSync7(fd);
12537
+ closeSync9(fd);
11549
12538
  } catch {
11550
12539
  }
11551
12540
  }
@@ -11556,23 +12545,23 @@ function claudeTranscriptFile(sessionId, cwd, home = claudeHome()) {
11556
12545
  const name = `${sessionId}.jsonl`;
11557
12546
  const exists = (f) => {
11558
12547
  try {
11559
- return statSync13(f).isFile();
12548
+ return statSync16(f).isFile();
11560
12549
  } catch {
11561
12550
  return false;
11562
12551
  }
11563
12552
  };
11564
12553
  if (cwd) {
11565
- const f = join34(home, "projects", projectDirName(cwd), name);
12554
+ const f = join37(home, "projects", projectDirName(cwd), name);
11566
12555
  if (exists(f)) return f;
11567
12556
  }
11568
12557
  let dirs = [];
11569
12558
  try {
11570
- dirs = readdirSync9(join34(home, "projects"));
12559
+ dirs = readdirSync10(join37(home, "projects"));
11571
12560
  } catch {
11572
12561
  return null;
11573
12562
  }
11574
12563
  for (const d of dirs) {
11575
- const f = join34(home, "projects", d, name);
12564
+ const f = join37(home, "projects", d, name);
11576
12565
  if (exists(f)) return f;
11577
12566
  }
11578
12567
  return null;
@@ -11589,9 +12578,272 @@ function shouldSettleInterrupt(st, markerAt, now) {
11589
12578
  return true;
11590
12579
  }
11591
12580
 
12581
+ // src/daemon/qoderAppDb.ts
12582
+ import { execFile as execFile9 } from "child_process";
12583
+ import { createHash as createHash4 } from "crypto";
12584
+ import { existsSync as existsSync23 } from "fs";
12585
+ import { access, open, readdir, stat } from "fs/promises";
12586
+ import { createRequire as createRequire5 } from "module";
12587
+ import { basename as basename11, dirname as dirname23, isAbsolute as isAbsolute4, join as join38 } from "path";
12588
+ var QODER_APP_TOOLS = ["qoder_cn", "qoder"];
12589
+ var DB_SCRIPT = String.raw`
12590
+ const { DatabaseSync } = require('node:sqlite');
12591
+ const NEED = {
12592
+ register: {
12593
+ chat_sessions: ['session_id','session_kind','conversation_mode','product_mode','title','cwd','execution_kind','workspace_id','model','permission_mode','additional_direcotries','extra_json','created_at','updated_at','execution_target_json','registration_json','archived','unread','deleted_at'],
12594
+ workspaces: ['workspace_id','name','workspace_source','root_paths_json','product_mode','extra_json','created_at','updated_at','archived','deleted_at'],
12595
+ },
12596
+ clear: { chat_session_messages: ['session_id','source'] },
12597
+ };
12598
+ const isBusy = (e) => /SQLITE_BUSY|database is locked|database table is locked/i.test(String(e && (e.code || '') + ' ' + (e.message || e)));
12599
+ const db = new DatabaseSync(input.db);
12600
+ try {
12601
+ db.exec('PRAGMA busy_timeout = ' + Math.max(0, Math.min(10000, Math.floor(Number(input.busyMs) || 3000))));
12602
+ const need = NEED[input.op];
12603
+ if (!need) return { status: 'error', detail: 'bad op' };
12604
+ for (const t of Object.keys(need)) {
12605
+ const have = new Set(db.prepare('PRAGMA table_info(' + t + ')').all().map((r) => r.name));
12606
+ const miss = need[t].filter((c) => !have.has(c));
12607
+ if (miss.length) return { status: 'schema', detail: t + ':' + miss.join(',') };
12608
+ }
12609
+ if (input.op === 'clear') {
12610
+ try {
12611
+ const r = db.prepare("delete from chat_session_messages where session_id = ? and source = 'cli-import'").run(input.sessionId);
12612
+ return { status: 'cleared', n: Number(r.changes) };
12613
+ } catch (e) {
12614
+ if (isBusy(e)) return { status: 'busy' };
12615
+ throw e;
12616
+ }
12617
+ }
12618
+ try {
12619
+ db.exec('BEGIN IMMEDIATE');
12620
+ } catch (e) {
12621
+ if (isBusy(e)) return { status: 'busy' };
12622
+ throw e;
12623
+ }
12624
+ try {
12625
+ if (db.prepare('select 1 as x from chat_sessions where session_id = ?').get(input.sessionId)) {
12626
+ db.exec('ROLLBACK');
12627
+ return { status: 'exists' };
12628
+ }
12629
+ const ws = db.prepare('select archived, deleted_at from workspaces where workspace_id = ?').get(input.wsid);
12630
+ let wsUse = null;
12631
+ if (!ws) {
12632
+ db.prepare("insert into workspaces(workspace_id,name,workspace_source,root_paths_json,product_mode,extra_json,created_at,updated_at,archived,deleted_at) values(?,?,'local',?,'coding','{}',?,?,0,NULL) on conflict(workspace_id) do nothing")
12633
+ .run(input.wsid, input.wsName, JSON.stringify([input.cwd]), input.now, input.now);
12634
+ wsUse = input.wsid;
12635
+ } else if (Number(ws.archived) === 0 && ws.deleted_at == null) {
12636
+ wsUse = input.wsid;
12637
+ }
12638
+ const extra = { source: 'workspace', cliSessionReady: true, modelSource: { kind: 'system' }, provisionalTitle: input.title, titleResolved: true, titleSource: 'user', titleRevision: 0 };
12639
+ const target = { mode: 'local', host: { id: 'local', kind: 'local', label: 'Local' }, workspace: { id: input.wsid, path: input.cwd } };
12640
+ const r = db.prepare("insert into chat_sessions(session_id,session_kind,conversation_mode,product_mode,title,cwd,execution_kind,workspace_id,model,permission_mode,additional_direcotries,extra_json,created_at,updated_at,execution_target_json,archived,unread) values(?,'standard','normal','coding',?,?,'local',?,'auto','auto','{}',?,?,?,?,0,1) on conflict(session_id) do nothing")
12641
+ .run(input.sessionId, input.title, input.cwd, wsUse, JSON.stringify(extra), input.createdAt, input.updatedAt, JSON.stringify(target));
12642
+ db.exec('COMMIT');
12643
+ return { status: Number(r.changes) > 0 ? 'inserted' : 'exists', detail: wsUse ? 'ws' : 'no_ws' };
12644
+ } catch (e) {
12645
+ try { db.exec('ROLLBACK'); } catch (_) {}
12646
+ if (isBusy(e)) return { status: 'busy' };
12647
+ throw e;
12648
+ }
12649
+ } finally {
12650
+ db.close();
12651
+ }
12652
+ `;
12653
+ function electronScript() {
12654
+ return `let out;try{const input=JSON.parse(require('fs').readFileSync(0,'utf8'));out=(function(require,input){${DB_SCRIPT}})(require,input)}catch(e){out={status:'error',detail:String(e&&e.message||e).slice(0,200)}}process.stdout.write(JSON.stringify(out))`;
12655
+ }
12656
+ function workspaceId(cwd) {
12657
+ return "ws_" + createHash4("sha256").update("local\0" + cwd).digest("hex");
12658
+ }
12659
+ function qoderConfigDir(tool) {
12660
+ return dirname23(tool === "qoder" ? qoderSettingsPath() : qoderCnSettingsPath());
12661
+ }
12662
+ async function findTranscript(configDir, sessionId) {
12663
+ if (!SESSION_UUID_RE.test(sessionId)) return null;
12664
+ const root = join38(configDir, "projects");
12665
+ let dirs;
12666
+ try {
12667
+ dirs = await readdir(root);
12668
+ } catch {
12669
+ return null;
12670
+ }
12671
+ for (const d of dirs) {
12672
+ const p = join38(root, d, `${sessionId}.jsonl`);
12673
+ try {
12674
+ await access(p);
12675
+ return p;
12676
+ } catch {
12677
+ }
12678
+ }
12679
+ return null;
12680
+ }
12681
+ var SLICE2 = 64 * 1024;
12682
+ async function readSlice2(p, from) {
12683
+ const fh = await open(p, "r");
12684
+ try {
12685
+ const size = (await fh.stat()).size;
12686
+ const len = Math.min(size, SLICE2);
12687
+ const buf = Buffer.alloc(len);
12688
+ await fh.read(buf, 0, len, from === "head" ? 0 : size - len);
12689
+ return buf.toString("utf8");
12690
+ } finally {
12691
+ await fh.close();
12692
+ }
12693
+ }
12694
+ function tsOf2(v) {
12695
+ if (typeof v === "number" && Number.isFinite(v) && v > 1e12) return Math.floor(v);
12696
+ if (typeof v === "string") {
12697
+ const n = Date.parse(v);
12698
+ if (Number.isFinite(n)) return n;
12699
+ }
12700
+ return null;
12701
+ }
12702
+ function lines2(text) {
12703
+ const out = [];
12704
+ for (const l of text.split("\n")) {
12705
+ try {
12706
+ const o = JSON.parse(l);
12707
+ if (o && typeof o === "object") out.push(o);
12708
+ } catch {
12709
+ }
12710
+ }
12711
+ return out;
12712
+ }
12713
+ function userText4(o) {
12714
+ if (o.type !== "user") return "";
12715
+ const c = o.message?.content;
12716
+ if (typeof c === "string") return c;
12717
+ if (Array.isArray(c)) {
12718
+ for (const b of c) if (b?.type === "text" && typeof b.text === "string") return b.text;
12719
+ }
12720
+ return "";
12721
+ }
12722
+ async function transcriptInfo(p) {
12723
+ const head = lines2(await readSlice2(p, "head"));
12724
+ const tail2 = lines2(await readSlice2(p, "tail"));
12725
+ const mtime = Math.floor((await stat(p)).mtimeMs);
12726
+ const hs = head.map((o) => tsOf2(o.timestamp)).filter((n) => n !== null);
12727
+ const ts = tail2.map((o) => tsOf2(o.timestamp)).filter((n) => n !== null);
12728
+ const createdAt = hs.length ? Math.min(...hs) : mtime;
12729
+ const updatedAt = Math.max(createdAt, ts.length ? Math.max(...ts) : mtime);
12730
+ let title = "";
12731
+ for (const o of head) {
12732
+ title = cleanTitle(userText4(o));
12733
+ if (title) break;
12734
+ }
12735
+ return { createdAt, updatedAt, title };
12736
+ }
12737
+ var requireHere3 = createRequire5(import.meta.url);
12738
+ function loadNodeSqlite2() {
12739
+ const orig = process.emitWarning;
12740
+ process.emitWarning = function(...args) {
12741
+ if (String(args[0] ?? "").includes("SQLite")) return;
12742
+ return orig.apply(process, args);
12743
+ };
12744
+ try {
12745
+ return requireHere3("node:sqlite");
12746
+ } finally {
12747
+ process.emitWarning = orig;
12748
+ }
12749
+ }
12750
+ var defaultQoderAppDeps = () => ({
12751
+ now: () => Date.now(),
12752
+ dbPath: (tool) => qoderDbPaths(tool)[0] ?? "",
12753
+ configDir: qoderConfigDir,
12754
+ loadSqlite: loadNodeSqlite2,
12755
+ electron: (tool) => {
12756
+ try {
12757
+ const loc = locate(tool);
12758
+ return loc?.env?.ELECTRON_RUN_AS_NODE === "1" ? loc.file : null;
12759
+ } catch {
12760
+ return null;
12761
+ }
12762
+ },
12763
+ runElectron: (bin, script, input) => new Promise((resolve10, reject) => {
12764
+ const env = { ...process.env, ELECTRON_RUN_AS_NODE: "1" };
12765
+ const c = execFile9(
12766
+ bin,
12767
+ ["-e", script],
12768
+ { env, timeout: 15e3, windowsHide: true, maxBuffer: 1024 * 1024 },
12769
+ (err, stdout7) => err ? reject(err) : resolve10(String(stdout7))
12770
+ );
12771
+ c.stdin?.on("error", () => {
12772
+ });
12773
+ c.stdin?.end(input);
12774
+ })
12775
+ });
12776
+ var IN_PROCESS_BUSY_MS = 500;
12777
+ async function runDb2(tool, input, d) {
12778
+ let mod = null;
12779
+ try {
12780
+ mod = d.loadSqlite();
12781
+ } catch {
12782
+ mod = null;
12783
+ }
12784
+ if (mod) {
12785
+ const fn = new Function("require", "input", DB_SCRIPT);
12786
+ try {
12787
+ return fn((id) => id === "node:sqlite" ? mod : requireHere3(id), { ...input, busyMs: IN_PROCESS_BUSY_MS });
12788
+ } catch (e) {
12789
+ return { status: "error", detail: String(e?.message ?? e).slice(0, 200) };
12790
+ }
12791
+ }
12792
+ const bin = d.electron(tool);
12793
+ if (!bin) return "no_sqlite";
12794
+ try {
12795
+ const out = JSON.parse(await d.runElectron(bin, electronScript(), JSON.stringify(input)));
12796
+ return out && typeof out.status === "string" ? out : { status: "error", detail: "bad output" };
12797
+ } catch (e) {
12798
+ return { status: "error", detail: `electron: ${String(e?.message ?? e).slice(0, 160)}` };
12799
+ }
12800
+ }
12801
+ async function syncQoderApp(job, d = defaultQoderAppDeps()) {
12802
+ try {
12803
+ if (!QODER_APP_TOOLS.includes(job.tool)) return "skip tool";
12804
+ if (!SESSION_UUID_RE.test(job.sessionId)) return "skip bad_session";
12805
+ const db = d.dbPath(job.tool);
12806
+ if (!db || !existsSync23(db)) return "skip no_db";
12807
+ if (job.kind === "clear") {
12808
+ const r2 = await runDb2(job.tool, { op: "clear", db, sessionId: job.sessionId }, d);
12809
+ return r2 === "no_sqlite" ? "skip no_sqlite" : describe2(r2);
12810
+ }
12811
+ if (!job.cwd || !isAbsolute4(job.cwd)) return "skip bad_cwd";
12812
+ const tp = await findTranscript(d.configDir(job.tool), job.sessionId);
12813
+ if (!tp) return "skip no_transcript";
12814
+ const info = await transcriptInfo(tp);
12815
+ const name = basename11(job.cwd) || job.cwd;
12816
+ const title = info.title || cleanTitle(job.fallbackTitle ?? "") || name;
12817
+ const r = await runDb2(
12818
+ job.tool,
12819
+ {
12820
+ op: "register",
12821
+ db,
12822
+ sessionId: job.sessionId,
12823
+ cwd: job.cwd,
12824
+ title,
12825
+ wsid: workspaceId(job.cwd),
12826
+ wsName: name,
12827
+ createdAt: info.createdAt,
12828
+ updatedAt: info.updatedAt,
12829
+ now: d.now()
12830
+ },
12831
+ d
12832
+ );
12833
+ return r === "no_sqlite" ? "skip no_sqlite" : describe2(r);
12834
+ } catch (e) {
12835
+ return `error ${String(e?.message ?? e).slice(0, 160)}`;
12836
+ }
12837
+ }
12838
+ function describe2(r) {
12839
+ if (r.status === "schema") return `skip schema ${r.detail ?? ""}`.trim();
12840
+ if (r.status === "busy") return "skip busy";
12841
+ return [r.status, r.n !== void 0 ? `n=${r.n}` : "", r.detail ?? ""].filter(Boolean).join(" ");
12842
+ }
12843
+
11592
12844
  // src/daemon/wake.ts
11593
12845
  import { randomBytes as randomBytes6 } from "crypto";
11594
- import { rmSync as rmSync15 } from "fs";
12846
+ import { rmSync as rmSync17 } from "fs";
11595
12847
  import { createServer as createServer3 } from "net";
11596
12848
  var BUSY_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
11597
12849
  var STOP_MIN_MS = 1500;
@@ -11886,7 +13138,7 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
11886
13138
  if (platform !== "win32") {
11887
13139
  if (!ensureChannelDir(path)) return Promise.resolve(null);
11888
13140
  try {
11889
- rmSync15(path, { force: true });
13141
+ rmSync17(path, { force: true });
11890
13142
  } catch {
11891
13143
  }
11892
13144
  }
@@ -11988,24 +13240,24 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
11988
13240
  }
11989
13241
 
11990
13242
  // src/core/appended.ts
11991
- import { chmodSync as chmodSync11, mkdirSync as mkdirSync25, readFileSync as readFileSync25, renameSync as renameSync17, rmSync as rmSync16, writeFileSync as writeFileSync23 } from "fs";
11992
- import { join as join35 } from "path";
13243
+ import { chmodSync as chmodSync11, mkdirSync as mkdirSync27, readFileSync as readFileSync28, renameSync as renameSync18, rmSync as rmSync18, writeFileSync as writeFileSync25 } from "fs";
13244
+ import { join as join39 } from "path";
11993
13245
  var APPENDED_MAX_ITEMS = 5;
11994
13246
  var APPENDED_TEXT_MAX = 500;
11995
13247
  var APPENDED_RESULT_MAX = 800;
11996
13248
  var APPENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
11997
13249
  function appendedDir() {
11998
- return join35(deskbuddyHome(), "appended");
13250
+ return join39(deskbuddyHome(), "appended");
11999
13251
  }
12000
13252
  function appendedPath(sessionId) {
12001
13253
  const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
12002
- return safe ? join35(appendedDir(), `${safe}.json`) : null;
13254
+ return safe ? join39(appendedDir(), `${safe}.json`) : null;
12003
13255
  }
12004
13256
  function readAppended(sessionId) {
12005
13257
  const file = appendedPath(sessionId);
12006
13258
  if (!file) return [];
12007
13259
  try {
12008
- const d = JSON.parse(readFileSync25(file, "utf8"));
13260
+ const d = JSON.parse(readFileSync28(file, "utf8"));
12009
13261
  return Array.isArray(d.items) ? d.items : [];
12010
13262
  } catch {
12011
13263
  return [];
@@ -12018,26 +13270,26 @@ function writeAppended(sessionId, item) {
12018
13270
  const items = readAppended(sessionId).concat([
12019
13271
  { text: item.text.slice(0, APPENDED_TEXT_MAX), result: item.result.slice(0, APPENDED_RESULT_MAX), ok: item.ok, at: item.at }
12020
13272
  ]);
12021
- mkdirSync25(appendedDir(), { recursive: true, mode: 448 });
13273
+ mkdirSync27(appendedDir(), { recursive: true, mode: 448 });
12022
13274
  const tmp = `${file}.${process.pid}.tmp`;
12023
- writeFileSync23(tmp, JSON.stringify({ items: items.slice(-APPENDED_MAX_ITEMS) }), { encoding: "utf8", mode: 384 });
13275
+ writeFileSync25(tmp, JSON.stringify({ items: items.slice(-APPENDED_MAX_ITEMS) }), { encoding: "utf8", mode: 384 });
12024
13276
  chmodSync11(tmp, 384);
12025
- renameSync17(tmp, file);
13277
+ renameSync18(tmp, file);
12026
13278
  } catch {
12027
13279
  }
12028
13280
  }
12029
13281
  var RUNNING_TTL_MS = 60 * 60 * 1e3;
12030
13282
  function runningPath(sessionId) {
12031
13283
  const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
12032
- return safe ? join35(deskbuddyHome(), "running", `${safe}.json`) : null;
13284
+ return safe ? join39(deskbuddyHome(), "running", `${safe}.json`) : null;
12033
13285
  }
12034
13286
  function writeRunning(sessionId, now = Date.now()) {
12035
13287
  const file = runningPath(sessionId);
12036
13288
  if (!file) return;
12037
13289
  try {
12038
- mkdirSync25(join35(deskbuddyHome(), "running"), { recursive: true, mode: 448 });
12039
- writeFileSync23(`${file}.tmp`, JSON.stringify({ at: now, pid: process.pid }), { encoding: "utf8", mode: 384 });
12040
- renameSync17(`${file}.tmp`, file);
13290
+ mkdirSync27(join39(deskbuddyHome(), "running"), { recursive: true, mode: 448 });
13291
+ writeFileSync25(`${file}.tmp`, JSON.stringify({ at: now, pid: process.pid }), { encoding: "utf8", mode: 384 });
13292
+ renameSync18(`${file}.tmp`, file);
12041
13293
  } catch {
12042
13294
  }
12043
13295
  }
@@ -12045,7 +13297,7 @@ function removeRunning(sessionId) {
12045
13297
  const file = runningPath(sessionId);
12046
13298
  if (!file) return;
12047
13299
  try {
12048
- rmSync16(file, { force: true });
13300
+ rmSync18(file, { force: true });
12049
13301
  } catch {
12050
13302
  }
12051
13303
  }
@@ -12082,8 +13334,8 @@ function resultSummary(tool, stdout7) {
12082
13334
  }
12083
13335
 
12084
13336
  // src/core/dshMailbox.ts
12085
- import { chmodSync as chmodSync12, existsSync as existsSync19, mkdirSync as mkdirSync26, readdirSync as readdirSync10, readFileSync as readFileSync26, renameSync as renameSync18, rmSync as rmSync17, writeFileSync as writeFileSync24 } from "fs";
12086
- import { join as join36 } from "path";
13337
+ import { chmodSync as chmodSync12, existsSync as existsSync24, mkdirSync as mkdirSync28, readdirSync as readdirSync11, readFileSync as readFileSync29, renameSync as renameSync19, rmSync as rmSync19, writeFileSync as writeFileSync26 } from "fs";
13338
+ import { join as join40 } from "path";
12087
13339
  var DSH_HEARTBEAT_FRESH_MS = 5 * 1e3;
12088
13340
  var DSH_ACK_TIMEOUT_MS = 8 * 1e3;
12089
13341
  var ACK_POLL_MS = 200;
@@ -12094,13 +13346,13 @@ var REVOKE_RETRY_MS = 50;
12094
13346
  var SID_RE3 = /^[A-Za-z0-9_-]{1,128}$/;
12095
13347
  var IID_RE = /^[A-Za-z0-9_-]{1,128}$/;
12096
13348
  function dshMailboxDir(home = deskbuddyHome()) {
12097
- return join36(home, "dsh");
13349
+ return join40(home, "dsh");
12098
13350
  }
12099
13351
  function sub(home, name) {
12100
- return join36(dshMailboxDir(home), name);
13352
+ return join40(dshMailboxDir(home), name);
12101
13353
  }
12102
13354
  function ensureDir2(p) {
12103
- mkdirSync26(p, { recursive: true, mode: 448 });
13355
+ mkdirSync28(p, { recursive: true, mode: 448 });
12104
13356
  try {
12105
13357
  chmodSync12(p, 448);
12106
13358
  } catch {
@@ -12108,8 +13360,8 @@ function ensureDir2(p) {
12108
13360
  }
12109
13361
  function writeJson(path, value) {
12110
13362
  const tmp = `${path}.${process.pid}.tmp`;
12111
- writeFileSync24(tmp, JSON.stringify(value), { encoding: "utf8", mode: 384 });
12112
- renameSync18(tmp, path);
13363
+ writeFileSync26(tmp, JSON.stringify(value), { encoding: "utf8", mode: 384 });
13364
+ renameSync19(tmp, path);
12113
13365
  }
12114
13366
  var defaultPidAlive = (pid) => {
12115
13367
  try {
@@ -12127,7 +13379,7 @@ function dshLiveFor(sessionId, d0 = {}) {
12127
13379
  const d = deps(d0);
12128
13380
  let names;
12129
13381
  try {
12130
- names = readdirSync10(sub(d.home, "alive"));
13382
+ names = readdirSync11(sub(d.home, "alive"));
12131
13383
  } catch {
12132
13384
  return null;
12133
13385
  }
@@ -12135,7 +13387,7 @@ function dshLiveFor(sessionId, d0 = {}) {
12135
13387
  if (!/^\d+\.json$/.test(n)) continue;
12136
13388
  let hb;
12137
13389
  try {
12138
- hb = JSON.parse(readFileSync26(join36(sub(d.home, "alive"), n), "utf8"));
13390
+ hb = JSON.parse(readFileSync29(join40(sub(d.home, "alive"), n), "utf8"));
12139
13391
  } catch {
12140
13392
  continue;
12141
13393
  }
@@ -12155,9 +13407,9 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
12155
13407
  const ackDir = sub(d.home, "ack");
12156
13408
  ensureDir2(inbox);
12157
13409
  ensureDir2(ackDir);
12158
- const letter = join36(inbox, `${sessionId}.${msg.instructionId}.json`);
12159
- const ackPath = join36(ackDir, `${msg.instructionId}.json`);
12160
- if (existsSync19(letter)) return { ok: false, reason: "inbox_busy" };
13410
+ const letter = join40(inbox, `${sessionId}.${msg.instructionId}.json`);
13411
+ const ackPath = join40(ackDir, `${msg.instructionId}.json`);
13412
+ if (existsSync24(letter)) return { ok: false, reason: "inbox_busy" };
12161
13413
  const writtenAt = d.now();
12162
13414
  writeJson(letter, { instructionId: msg.instructionId, text: msg.text, at: writtenAt });
12163
13415
  const sleep3 = d0.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -12165,19 +13417,19 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
12165
13417
  let claimed = false;
12166
13418
  let revokeFailed = false;
12167
13419
  for (; ; ) {
12168
- if (existsSync19(ackPath)) {
13420
+ if (existsSync24(ackPath)) {
12169
13421
  let ack = { ok: false, reason: "bad_ack" };
12170
13422
  try {
12171
- const v = JSON.parse(readFileSync26(ackPath, "utf8"));
13423
+ const v = JSON.parse(readFileSync29(ackPath, "utf8"));
12172
13424
  if (v && typeof v.ok === "boolean") ack = v;
12173
13425
  } catch {
12174
13426
  }
12175
- rmSync17(ackPath, { force: true });
13427
+ rmSync19(ackPath, { force: true });
12176
13428
  return ack;
12177
13429
  }
12178
13430
  if (d.now() >= deadline) {
12179
13431
  if (revokeFailed) {
12180
- if (existsSync19(letter)) return { ok: false, reason: "revoke_failed" };
13432
+ if (existsSync24(letter)) return { ok: false, reason: "revoke_failed" };
12181
13433
  revokeFailed = false;
12182
13434
  claimed = true;
12183
13435
  deadline = d.now() + DSH_ACK_GRACE_MS;
@@ -12203,28 +13455,28 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
12203
13455
  async function revoke(letter, sleep3) {
12204
13456
  for (let i = 0; i <= REVOKE_RETRIES; i++) {
12205
13457
  try {
12206
- renameSync18(letter, `${letter}.revoked`);
12207
- rmSync17(`${letter}.revoked`, { force: true });
13458
+ renameSync19(letter, `${letter}.revoked`);
13459
+ rmSync19(`${letter}.revoked`, { force: true });
12208
13460
  return "revoked";
12209
13461
  } catch (e) {
12210
13462
  if (e?.code === "ENOENT") return "claimed";
12211
13463
  if (i < REVOKE_RETRIES) await sleep3(REVOKE_RETRY_MS);
12212
13464
  }
12213
13465
  }
12214
- return existsSync19(letter) ? "failed" : "claimed";
13466
+ return existsSync24(letter) ? "failed" : "claimed";
12215
13467
  }
12216
13468
  function dshClearInbox(home = deskbuddyHome()) {
12217
13469
  const inbox = sub(home, "inbox");
12218
13470
  let names;
12219
13471
  try {
12220
- names = readdirSync10(inbox);
13472
+ names = readdirSync11(inbox);
12221
13473
  } catch {
12222
13474
  return 0;
12223
13475
  }
12224
13476
  let n = 0;
12225
13477
  for (const f of names) {
12226
13478
  try {
12227
- rmSync17(join36(inbox, f), { force: true });
13479
+ rmSync19(join40(inbox, f), { force: true });
12228
13480
  n++;
12229
13481
  } catch {
12230
13482
  }
@@ -12256,7 +13508,7 @@ function defaultCodexAppServers() {
12256
13508
  }
12257
13509
  function opencodeReady() {
12258
13510
  const mod = ocPluginModule(installedHookPath());
12259
- return isOcPluginInstalled(opencodePluginPath()) && existsSync20(mod) && existsSync20(join37(dirname19(mod), "ocTranslate.mjs"));
13511
+ return isOcPluginInstalled(opencodePluginPath()) && existsSync25(mod) && existsSync25(join41(dirname24(mod), "ocTranslate.mjs"));
12260
13512
  }
12261
13513
  var INTERNAL_PROMPT_MARK = "[DeskBuddy]";
12262
13514
  function orphanResumeText(summary2) {
@@ -12290,7 +13542,7 @@ function shouldIdleWindow(last, sessionId, markerAt) {
12290
13542
  }
12291
13543
  function transcriptSig(file) {
12292
13544
  try {
12293
- const st = statSync14(file);
13545
+ const st = statSync17(file);
12294
13546
  return `${st.size}:${st.mtimeMs}`;
12295
13547
  } catch {
12296
13548
  return "";
@@ -12397,9 +13649,15 @@ var Daemon = class {
12397
13649
  }),
12398
13650
  onLink: deps2.onLink ?? (() => {
12399
13651
  }),
13652
+ qoderAppSync: deps2.qoderAppSync ?? ((job) => syncQoderApp(job)),
12400
13653
  dshLive: deps2.dshLive ?? ((sid) => dshLiveFor(sid) != null),
12401
13654
  dshDeliver: deps2.dshDeliver ?? ((sid, msg) => dshDeliver(sid, msg)),
12402
- loginGuide: deps2.loginGuide ?? defaultLoginGuide(log, deps2.now ?? (() => Date.now()))
13655
+ loginGuide: deps2.loginGuide ?? defaultLoginGuide(log, deps2.now ?? (() => Date.now()), () => {
13656
+ const offer = defaultWorkbuddyOffer(log);
13657
+ if (offer) void offer.maybeAsk("runtime").catch(() => {
13658
+ });
13659
+ }),
13660
+ workbuddyAppSync: deps2.workbuddyAppSync ?? ((job) => registerWorkbuddySession(job))
12403
13661
  };
12404
13662
  this.#wake = new WakeHub({
12405
13663
  now: this.#deps.now,
@@ -12640,9 +13898,9 @@ var Daemon = class {
12640
13898
  }
12641
13899
  /** 会话的 transcript:hook 记在状态里的优先;没有再按目录找,找不到的一段时间内不再找 */
12642
13900
  #transcriptOf(st, now) {
12643
- if (st.transcriptPath && existsSync20(st.transcriptPath)) return st.transcriptPath;
13901
+ if (st.transcriptPath && existsSync25(st.transcriptPath)) return st.transcriptPath;
12644
13902
  const cached = this.#transcripts.get(st.sessionId);
12645
- if (cached && existsSync20(cached)) return cached;
13903
+ if (cached && existsSync25(cached)) return cached;
12646
13904
  const missAt = this.#transcriptMiss.get(st.sessionId);
12647
13905
  if (missAt !== void 0 && now - missAt < TRANSCRIPT_MISS_TTL_MS) return null;
12648
13906
  const home = transcriptHome(st.tool);
@@ -12938,6 +14196,11 @@ var Daemon = class {
12938
14196
  this.#seen.save();
12939
14197
  cmd.receivedAt = this.#deps.now();
12940
14198
  log(`\u6536\u5230\u6307\u4EE4 ${cmd.instructionId} tool=${cmd.tool} ${cmd.sessionId ? `\u56DE\u590D ${cmd.sessionId.slice(0, 8)}` : "\u65B0\u5EFA"}`);
14199
+ if (QODER_APP_TOOLS.includes(cmd.tool) && cmd.sessionId && !SESSION_UUID_RE.test(cmd.sessionId)) {
14200
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u4F1A\u8BDD\u53F7\u4E0D\u662F UUID\uFF0C\u62D2\u6536`);
14201
+ this.#report(cmd, cmd.sessionId, "rejected", "", "session_gone");
14202
+ return;
14203
+ }
12941
14204
  this.#route(cmd);
12942
14205
  }
12943
14206
  /**
@@ -13481,7 +14744,7 @@ var Daemon = class {
13481
14744
  this.#report(cmd, sessionId, "rejected", "", "tool_unsupported");
13482
14745
  return;
13483
14746
  }
13484
- log(`\u6267\u884C ${cmd.instructionId} tool=${cmd.tool} ${sessionId ? "resume" : "new"} cwd=\u2026/${basename9(cwd)} len=${cmd.text.length}`);
14747
+ log(`\u6267\u884C ${cmd.instructionId} tool=${cmd.tool} ${sessionId ? "resume" : "new"} cwd=\u2026/${basename12(cwd)} len=${cmd.text.length}`);
13485
14748
  const t0 = this.#deps.now();
13486
14749
  const result = await this.#run(plan, cmd.tool, runId, sessionId, cwd, () => this.#report(cmd, sessionId, "started", "headless"), cmd.text);
13487
14750
  if (cmd.tool === "deepseek_harness" && !("fail" in result)) {
@@ -13514,11 +14777,37 @@ var Daemon = class {
13514
14777
  );
13515
14778
  if (notLoggedIn) this.#guideLogin(cmd.tool);
13516
14779
  }
14780
+ let newId = null;
13517
14781
  if (!sessionId) {
13518
- const newId = result.sessionId ?? parseSessionId(cmd.tool, result.stdout);
14782
+ newId = result.sessionId ?? parseSessionId(cmd.tool, result.stdout);
13519
14783
  log(`\u6307\u4EE4 ${cmd.instructionId} ${newId ? `\u5EFA\u51FA\u4F1A\u8BDD ${newId}` : "\u672A\u80FD\u89E3\u6790\u51FA\u4F1A\u8BDD id"}`);
14784
+ if (cmd.tool === "workbuddy" && newId && result.ok) this.#syncWorkbuddyApp({ sessionId: newId, cwd, fallbackTitle: cmd.text });
14785
+ }
14786
+ if (QODER_APP_TOOLS.includes(cmd.tool) && (sessionId || newId)) {
14787
+ this.#syncQoderApp({
14788
+ tool: cmd.tool,
14789
+ kind: sessionId ? "clear" : "register",
14790
+ sessionId: sessionId || newId,
14791
+ cwd,
14792
+ fallbackTitle: cmd.text
14793
+ });
13520
14794
  }
13521
14795
  }
14796
+ #syncQoderApp(job) {
14797
+ const tag = `Qoder \u4F1A\u8BDD ${job.sessionId.slice(0, 8)} ${job.kind === "register" ? "\u767B\u8BB0\u8FDB\u684C\u9762\u7AEF\u5217\u8868" : "\u6E05\u684C\u9762\u7AEF\u6D88\u606F\u7F13\u5B58"}`;
14798
+ setImmediate(() => {
14799
+ let p;
14800
+ try {
14801
+ p = this.#deps.qoderAppSync(job);
14802
+ } catch (e) {
14803
+ p = Promise.resolve(`error ${e?.message ?? String(e)}`);
14804
+ }
14805
+ void Promise.resolve(p).then(
14806
+ (r) => log(`${tag}\uFF1A${r}`),
14807
+ (e) => log(`${tag}\u51FA\u9519\uFF1A${e?.message ?? String(e)}`)
14808
+ );
14809
+ });
14810
+ }
13522
14811
  /**
13523
14812
  * 命令行没登录:在电脑上开登录窗口(同一工具 30 分钟内一次,见 loginGuide.ts)。不阻塞回报链路。
13524
14813
  * 🔴 daemon 开的 WorkBuddy 窗口停在登录页(LoginRequired)不走这里:那个窗口本身就在登录页上
@@ -13531,6 +14820,19 @@ var Daemon = class {
13531
14820
  log(`${tool} \u767B\u5F55\u5F15\u5BFC\u51FA\u9519\uFF1A${e?.message ?? e}`);
13532
14821
  }
13533
14822
  }
14823
+ #syncWorkbuddyApp(job) {
14824
+ const tag = `WorkBuddy \u4F1A\u8BDD ${job.sessionId.slice(0, 8)} \u767B\u8BB0\u8FDB\u684C\u9762\u7AEF\u5217\u8868`;
14825
+ let p;
14826
+ try {
14827
+ p = this.#deps.workbuddyAppSync(job);
14828
+ } catch (e) {
14829
+ p = Promise.resolve(`error ${e?.message ?? String(e)}`);
14830
+ }
14831
+ void Promise.resolve(p).then(
14832
+ (r) => log(`${tag}\uFF1A${r}`),
14833
+ (e) => log(`${tag}\u51FA\u9519\uFF1A${e?.message ?? String(e)}`)
14834
+ );
14835
+ }
13534
14836
  #gateTarget(runId) {
13535
14837
  return { runId, nodePath: this.#deps.nodePath, gateScript: this.#deps.gateScript };
13536
14838
  }
@@ -13635,7 +14937,7 @@ var Daemon = class {
13635
14937
  * 起不了(关卡不可用 / 找不到可执行文件)返回 {fail};进程起来那一刻调 onStarted
13636
14938
  */
13637
14939
  async #run(plan, tool, runId, sessionId, cwd, onStarted, text = "") {
13638
- if (!existsSync20(this.#deps.nodePath) || !existsSync20(this.#deps.gateScript)) {
14940
+ if (!existsSync25(this.#deps.nodePath) || !existsSync25(this.#deps.gateScript)) {
13639
14941
  log(`\u6388\u6743\u5173\u5361\u4E0D\u53EF\u7528\uFF08node \u6216 gate.cjs \u627E\u4E0D\u5230\uFF09\uFF0C\u62D2\u7EDD\u6267\u884C ${tool}`);
13640
14942
  return { fail: "gate_unavailable" };
13641
14943
  }
@@ -13721,7 +15023,7 @@ async function runDaemonCommand(opts = {}) {
13721
15023
  }
13722
15024
  if (opts.foreground) process.env.DESKBUDDY_DAEMON_FOREGROUND = "1";
13723
15025
  try {
13724
- writeFileSync25(pidFilePath(), String(process.pid), "utf8");
15026
+ writeFileSync27(pidFilePath(), String(process.pid), "utf8");
13725
15027
  } catch {
13726
15028
  }
13727
15029
  let daemon;
@@ -13747,16 +15049,23 @@ async function runDaemonCommand(opts = {}) {
13747
15049
  log: (l) => log(`[\u81EA\u52A8\u5347\u7EA7] ${l}`)
13748
15050
  });
13749
15051
  updater?.start();
13750
- const autoEnabler = new AutoEnabler(
13751
- defaultAutoEnableDeps(
15052
+ const wbOffer = defaultWorkbuddyOffer((l) => log(l));
15053
+ const autoEnabler = new AutoEnabler({
15054
+ ...defaultAutoEnableDeps(
13752
15055
  (l) => log(l),
13753
15056
  (tools) => {
13754
15057
  d.reportAliveNow();
13755
15058
  void autoEnableLoginLog(tools, (t) => checkLogin(t, defaultCheckDeps()), (l) => log(l)).catch(() => {
13756
15059
  });
13757
15060
  }
13758
- )
13759
- );
15061
+ ),
15062
+ onTick: () => {
15063
+ if (wbOffer) void wbOffer.maybeAsk("daemon").catch(() => {
15064
+ });
15065
+ void flushPendingWorkbuddy().then((r) => r && log(`[WorkBuddy \u4F1A\u8BDD\u767B\u8BB0] ${r}`)).catch(() => {
15066
+ });
15067
+ }
15068
+ });
13760
15069
  autoEnabler.start();
13761
15070
  const shutdown = (sig) => {
13762
15071
  log(`\u6536\u5230 ${sig}\uFF0C\u9000\u51FA`);
@@ -13764,7 +15073,7 @@ async function runDaemonCommand(opts = {}) {
13764
15073
  autoEnabler.stop();
13765
15074
  daemon.stop();
13766
15075
  try {
13767
- rmSync18(pidFilePath(), { force: true });
15076
+ rmSync20(pidFilePath(), { force: true });
13768
15077
  } catch {
13769
15078
  }
13770
15079
  process.exit(0);
@@ -13845,28 +15154,28 @@ async function runDisable(tools, opts = {}) {
13845
15154
  }
13846
15155
 
13847
15156
  // src/commands/install.ts
13848
- import { mkdirSync as mkdirSync27, rmSync as rmSync19, writeFileSync as writeFileSync26 } from "fs";
13849
- import { join as join38 } from "path";
15157
+ import { mkdirSync as mkdirSync29, rmSync as rmSync21, writeFileSync as writeFileSync28 } from "fs";
15158
+ import { join as join42 } from "path";
13850
15159
 
13851
15160
  // src/commands/setup.ts
13852
15161
  import qrcode from "qrcode-terminal";
13853
15162
 
13854
15163
  // src/commands/remoteCwd.ts
13855
- import { existsSync as existsSync21, statSync as statSync15 } from "fs";
15164
+ import { existsSync as existsSync26, statSync as statSync18 } from "fs";
13856
15165
  import { createInterface } from "readline";
13857
- import { homedir as homedir11 } from "os";
13858
- import { isAbsolute as isAbsolute2, resolve as resolve8 } from "path";
13859
- function normalizePath(raw, home = homedir11()) {
15166
+ import { homedir as homedir12 } from "os";
15167
+ import { isAbsolute as isAbsolute5, resolve as resolve8 } from "path";
15168
+ function normalizePath(raw, home = homedir12()) {
13860
15169
  const s2 = (raw || "").trim();
13861
15170
  if (!s2) return null;
13862
15171
  const expanded = s2 === "~" ? home : s2.startsWith("~/") || process.platform === "win32" && s2.startsWith("~\\") ? resolve8(home, s2.slice(2)) : s2;
13863
- const abs = isAbsolute2(expanded) ? expanded : resolve8(process.cwd(), expanded);
15172
+ const abs = isAbsolute5(expanded) ? expanded : resolve8(process.cwd(), expanded);
13864
15173
  return abs;
13865
15174
  }
13866
15175
  function checkPath(abs) {
13867
- if (!existsSync21(abs)) return "missing";
15176
+ if (!existsSync26(abs)) return "missing";
13868
15177
  try {
13869
- return statSync15(abs).isDirectory() ? "ok" : "not_a_dir";
15178
+ return statSync18(abs).isDirectory() ? "ok" : "not_a_dir";
13870
15179
  } catch {
13871
15180
  return "missing";
13872
15181
  }
@@ -14063,11 +15372,11 @@ function inSandbox(env) {
14063
15372
  }
14064
15373
  function precheckWritable(dirs) {
14065
15374
  for (const dir of dirs) {
14066
- const probe = join38(dir, `.deskbuddy-probe-${process.pid}`);
15375
+ const probe = join42(dir, `.deskbuddy-probe-${process.pid}`);
14067
15376
  try {
14068
- mkdirSync27(dir, { recursive: true });
14069
- writeFileSync26(probe, "");
14070
- rmSync19(probe, { force: true });
15377
+ mkdirSync29(dir, { recursive: true });
15378
+ writeFileSync28(probe, "");
15379
+ rmSync21(probe, { force: true });
14071
15380
  } catch (e) {
14072
15381
  return { ok: false, dir, error: e.message };
14073
15382
  }
@@ -14088,12 +15397,16 @@ var defaultInstallDeps = () => ({
14088
15397
  loginCheck: (tools, out) => {
14089
15398
  const guide = defaultGuideDeps();
14090
15399
  return guide ? runLoginCheck(tools, out, guide) : Promise.resolve([]);
15400
+ },
15401
+ workbuddyPrompt: (out) => {
15402
+ const d = defaultPromptStartDeps();
15403
+ return d ? startStandalonePrompt("install", out, d) : "none";
14091
15404
  }
14092
15405
  });
14093
- function summary(out, lines2) {
15406
+ function summary(out, lines3) {
14094
15407
  out("");
14095
15408
  out("=== DeskBuddy \u5B89\u88C5\u7ED3\u679C ===");
14096
- for (const l of lines2) out(l);
15409
+ for (const l of lines3) out(l);
14097
15410
  out("\u8BF7\u628A\u4EE5\u4E0A\u7ED3\u679C\u539F\u6837\u544A\u8BC9\u7528\u6237\u3002");
14098
15411
  }
14099
15412
  async function runInstall(opts, deps2 = defaultInstallDeps()) {
@@ -14169,6 +15482,12 @@ async function runInstall(opts, deps2 = defaultInstallDeps()) {
14169
15482
  await deps2.refreshRegistry();
14170
15483
  out("");
14171
15484
  const r = tools.length > 0 ? deps2.installTools(tools, out) : { installed: [], failed: [] };
15485
+ let wbPrompt = "none";
15486
+ try {
15487
+ wbPrompt = deps2.workbuddyPrompt?.(out) ?? "none";
15488
+ } catch (e) {
15489
+ out(`\xB7 WorkBuddy \u72EC\u7ACB\u7248\u68C0\u67E5\u51FA\u9519\uFF1A${e?.message ?? e}\uFF08\u4E0D\u5F71\u54CD\u5B89\u88C5\uFF09`);
15490
+ }
14172
15491
  const bound = loadConfig();
14173
15492
  if (!bound.remoteCwd) {
14174
15493
  bound.remoteCwd = defaultRemoteCwd();
@@ -14183,7 +15502,7 @@ async function runInstall(opts, deps2 = defaultInstallDeps()) {
14183
15502
  }
14184
15503
  const label2 = (t) => TOOL_LABELS[t] ?? t;
14185
15504
  const ok = r.installed.length > 0;
14186
- const lines2 = [
15505
+ const lines3 = [
14187
15506
  `RESULT: ${ok ? "ok" : "partial"}`,
14188
15507
  `\u8D26\u53F7: ${redeemed.accountHint || "\uFF08\u672A\u77E5\uFF09"}${redeemed.reused ? "\uFF08\u8FD9\u53F0\u7535\u8111\u4E4B\u524D\u5DF2\u7ED1\u5B9A\u8BE5\u8D26\u53F7\uFF09" : ""}\u2014\u2014\u5982\u679C\u8FD9\u4E0D\u662F\u4F60\u7684\u8D26\u53F7\uFF0C\u8BF7\u7ACB\u5373\u8FD0\u884C ${npx} logout --yes`,
14189
15508
  `\u5DF2\u63A5\u5165: ${r.installed.map(label2).join("\u3001") || "\u65E0"}` + (r.failed.length > 0 ? ` \u672A\u63A5\u5165: ${r.failed.map((f) => `${label2(f.tool)}\uFF08${f.reason}\uFF09`).join("\u3001")}` : ""),
@@ -14191,24 +15510,26 @@ async function runInstall(opts, deps2 = defaultInstallDeps()) {
14191
15510
  `\u8FDC\u7A0B\u65B0\u5EFA\u4EFB\u52A1\u76EE\u5F55: ${loadConfig().remoteCwd}`
14192
15511
  ];
14193
15512
  const loginLine = summaryLine(logins);
14194
- if (loginLine) lines2.push(loginLine);
15513
+ if (loginLine) lines3.push(loginLine);
15514
+ const wbLine = promptSummaryLine(wbPrompt);
15515
+ if (wbLine) lines3.push(wbLine);
14195
15516
  if (ok) {
14196
15517
  const restart = r.installed.map(label2).join(" / ");
14197
- lines2.push(
15518
+ lines3.push(
14198
15519
  `\u4E0B\u4E00\u6B65: \u91CD\u542F ${restart} \u540E\u5F00\u59CB\u540C\u6B65\u72B6\u6001\uFF08\u6B63\u5728\u8FD0\u884C\u7684\u4F1A\u8BDD\u4E0D\u4F1A\u91CD\u65B0\u52A0\u8F7D hook\uFF09` + (r.installed.includes("codex") ? "\uFF1BCodex \u4E0B\u6B21\u542F\u52A8\u4F1A\u8981\u6C42\u4FE1\u4EFB DeskBuddy \u7684 hook\uFF0C\u8BF7\u9009\u62E9\u540C\u610F" : "")
14199
15520
  );
14200
15521
  } else {
14201
- lines2.push(
15522
+ lines3.push(
14202
15523
  tools.length === 0 ? `\u4E0B\u4E00\u6B65: \u8FD9\u53F0\u7535\u8111\u4E0A\u6CA1\u68C0\u6D4B\u5230\u652F\u6301\u7684\u667A\u80FD\u4F53\uFF08Claude Code / Codex / WorkBuddy / Qoder CN / Qoder / opencode / DeepSeek Harness\uFF09\u3002\u88C5\u597D\u540E\u8FD0\u884C ${npx} enable <\u5DE5\u5177\u540D>\uFF0C\u5982 claude_code` : `\u4E0B\u4E00\u6B65: \u624B\u52A8\u8FD0\u884C ${npx} enable ${tools.join(",")} \u91CD\u8BD5\uFF0C\u5E76\u628A\u62A5\u9519\u544A\u8BC9\u7528\u6237`
14203
15524
  );
14204
15525
  }
14205
- summary(out, lines2);
15526
+ summary(out, lines3);
14206
15527
  return ok ? EXIT.ok : EXIT.partial;
14207
15528
  }
14208
15529
 
14209
15530
  // src/commands/logout.ts
14210
15531
  import { createInterface as createInterface2 } from "readline";
14211
- import { existsSync as existsSync22, rmSync as rmSync20 } from "fs";
15532
+ import { existsSync as existsSync27, rmSync as rmSync22 } from "fs";
14212
15533
  function stdout4(line) {
14213
15534
  process.stdout.write(line + "\n");
14214
15535
  }
@@ -14239,8 +15560,8 @@ async function runLogout(opts = {}) {
14239
15560
  }
14240
15561
  }
14241
15562
  await runDisable([], { out, brief: true, keepAutostart: opts.keepAutostart });
14242
- if (existsSync22(home)) {
14243
- rmSync20(home, { recursive: true, force: true });
15563
+ if (existsSync27(home)) {
15564
+ rmSync22(home, { recursive: true, force: true });
14244
15565
  }
14245
15566
  out("");
14246
15567
  out("\u2705 \u5DF2\u6E05\u9664\u672C\u673A\u7684 DeskBuddy \u6570\u636E\u3002");
@@ -14276,58 +15597,63 @@ function pad(s2, width) {
14276
15597
  return s2 + " ".repeat(Math.max(1, width - w));
14277
15598
  }
14278
15599
  function formatStatus(input) {
14279
- const lines2 = [];
15600
+ const lines3 = [];
14280
15601
  const cfg = input.config;
14281
- lines2.push("DeskBuddy \u72B6\u6001");
15602
+ lines3.push("DeskBuddy \u72B6\u6001");
14282
15603
  const account = cfg.boundAt ? `\u5DF2\u6388\u6743\uFF08clientId ${cfg.clientId.slice(0, 4)}\u2026\uFF0C\u7ED1\u5B9A\u4E8E ${fmtTime(cfg.boundAt)}\uFF09` : "\u672A\u6388\u6743 \u2014\u2014 \u8FD0\u884C deskbuddy setup \u626B\u7801\u6388\u6743";
14283
- lines2.push(` ${pad("\u8D26\u53F7", 10)}${account}`);
14284
- lines2.push(` ${pad("\u670D\u52A1\u7AEF", 10)}${cfg.apiBase}\uFF08${environmentLabel(cfg.apiBase)}\uFF09`);
15604
+ lines3.push(` ${pad("\u8D26\u53F7", 10)}${account}`);
15605
+ lines3.push(` ${pad("\u670D\u52A1\u7AEF", 10)}${cfg.apiBase}\uFF08${environmentLabel(cfg.apiBase)}\uFF09`);
14285
15606
  if (cfg.boundAt && cfg.boundApiBase && cfg.boundApiBase !== cfg.apiBase) {
14286
- lines2.push(
15607
+ lines3.push(
14287
15608
  ` \u26A0\uFE0F \u6388\u6743\u65F6\u8FDE\u7684\u662F ${cfg.boundApiBase}\uFF08${environmentLabel(cfg.boundApiBase)}\uFF09\uFF0C\u4E0E\u5F53\u524D\u4E0D\u4E00\u81F4 \u2014\u2014 \u5F53\u524D\u73AF\u5883\u5E76\u4E0D\u8BA4\u8BC6\u8FD9\u53F0\u673A\u5668\uFF0C\u9700\u8981\u91CD\u65B0 deskbuddy setup`
14288
15609
  );
14289
15610
  }
14290
15611
  if (input.envOverride?.active) {
14291
- lines2.push(
15612
+ lines3.push(
14292
15613
  ` \u26A0\uFE0F \u73AF\u5883\u53D8\u91CF DESKBUDDY_API_BASE=${input.envOverride.envValue} \u6B63\u5728\u8986\u76D6 config.json \u91CC\u7684 ${input.envOverride.fileValue}\uFF08\u591A\u534A\u662F\u5199\u8FDB\u4E86 .zshrc \u4E4B\u7C7B\uFF09`
14293
15614
  );
14294
15615
  }
14295
15616
  if (input.tools.length === 0) {
14296
- lines2.push(` ${pad("\u5DF2\u542F\u7528", 10)}\u65E0 \u2014\u2014 \u8FD0\u884C deskbuddy enable claude_code,codex`);
15617
+ lines3.push(` ${pad("\u5DF2\u542F\u7528", 10)}\u65E0 \u2014\u2014 \u8FD0\u884C deskbuddy enable claude_code,codex`);
14297
15618
  } else {
14298
15619
  const desc = input.tools.map((t) => `${t.tool}\uFF08${t.installed ? "hook \u5DF2\u88C5" : "\u26A0\uFE0F hook \u672A\u88C5\uFF0C\u91CD\u8DD1 deskbuddy enable"}\uFF09`).join("\u3001");
14299
- lines2.push(` ${pad("\u5DF2\u542F\u7528", 10)}${desc}`);
15620
+ lines3.push(` ${pad("\u5DF2\u542F\u7528", 10)}${desc}`);
14300
15621
  }
14301
15622
  const versionNote = input.hookVersion === null ? "\u672A\u5B89\u88C5" : input.hookVersion === input.currentVersion ? `${input.hookVersion}\uFF08\u4E0E\u5F53\u524D\u5305\u4E00\u81F4\uFF09` : `${input.hookVersion} \u26A0\uFE0F \u5F53\u524D\u5305\u662F ${input.currentVersion}\uFF0C\u4E0B\u6B21\u8FD0\u884C\u4EFB\u610F deskbuddy \u547D\u4EE4\u4F1A\u81EA\u52A8\u6821\u51C6`;
14302
- lines2.push(` ${pad("hook \u7248\u672C", 10)}${versionNote}`);
15623
+ lines3.push(` ${pad("hook \u7248\u672C", 10)}${versionNote}`);
14303
15624
  if (input.windowWrap) {
14304
15625
  const w = input.windowWrap;
14305
15626
  const text = w.winPending ? WIN_PENDING : !w.installed ? "\u672A\u5B89\u88C5 \u2014\u2014 \u91CD\u8DD1 deskbuddy enable claude_code" : !w.inPath ? `\u672A\u751F\u6548\uFF08\u5F53\u524D shell \u7684 PATH \u91CC\u6CA1\u6709 ${w.binDir}\uFF0C\u65B0\u5F00\u4E00\u4E2A\u7EC8\u7AEF\u518D\u770B\uFF09` : !w.ptyOk ? `\u5DF2\u5F00\u542F\uFF0C\u4F46 ${w.ptyWhy ?? "node-pty \u6CA1\u88C5\u4E0A"}\uFF0C\u6682\u65F6\u964D\u7EA7\u4E3A\u76F4\u63A5\u8FD0\u884C` : "\u5DF2\u5F00\u542F\uFF08\u7EC8\u7AEF\u91CC\u6572 claude \u4F1A\u7ECF DeskBuddy \u6253\u5F00\uFF0C\u8BBE\u5907\u56DE\u590D\u80FD\u5199\u8FDB\u7A97\u53E3\uFF09";
14306
- lines2.push(` ${pad("\u7A97\u53E3\u63A5\u7BA1", 10)}${text}`);
15627
+ lines3.push(` ${pad("\u7A97\u53E3\u63A5\u7BA1", 10)}${text}`);
14307
15628
  }
14308
15629
  if (input.codexWrap) {
14309
15630
  const w = input.codexWrap;
14310
15631
  const text = w.winPending ? WIN_PENDING : !w.installed ? "\u672A\u5B89\u88C5 \u2014\u2014 \u91CD\u8DD1 deskbuddy enable codex" : !w.inPath ? `\u672A\u751F\u6548\uFF08\u5F53\u524D shell \u7684 PATH \u91CC\u6CA1\u6709 ${w.binDir}\uFF0C\u65B0\u5F00\u4E00\u4E2A\u7EC8\u7AEF\u518D\u770B\uFF09` : "\u5DF2\u5F00\u542F\uFF08\u7EC8\u7AEF\u91CC\u6572 codex \u4F1A\u7ED9\u8FD9\u4E2A\u7A97\u53E3\u8D77\u4E00\u4E2A app-server \u518D\u8FDE\u4E0A\u53BB\uFF1Bdaemon \u5728\u8DD1\u65F6\u8BBE\u5907\u6307\u4EE4\u80FD\u53D1\u8FDB\u7A97\u53E3\uFF09";
14311
- lines2.push(` ${pad("Codex\u7A97\u53E3", 10)}${text}`);
15632
+ lines3.push(` ${pad("Codex\u7A97\u53E3", 10)}${text}`);
15633
+ }
15634
+ if (input.workbuddyCli) {
15635
+ const w = input.workbuddyCli;
15636
+ const text = w.standalone ? `\u72EC\u7ACB\u7248 ${w.standalone}\uFF08\u8BBE\u5907\u4E0A\u53EF\u65B0\u5EFA / \u56DE\u590D\u4EFB\u52A1\uFF09` : w.app ? w.declined ? `\u53EA\u88C5\u4E86 App\uFF0C\u5DF2\u9009\u62E9\u6682\u4E0D\u5B89\u88C5\u72EC\u7ACB\u7248\uFF08${LIMITED_NOTE}\uFF09\uFF1B\u8FD0\u884C deskbuddy workbuddy-setup \u53EF\u518D\u88C5` : `\u53EA\u88C5\u4E86 App\uFF08${LIMITED_NOTE}\uFF09\uFF1B\u8FD0\u884C deskbuddy workbuddy-setup \u5B89\u88C5\u72EC\u7ACB\u7248\u547D\u4EE4\u884C` : "\u6CA1\u627E\u5230\uFF08\u88C5 WorkBuddy App \u6216\u8FD0\u884C deskbuddy workbuddy-setup \u88C5\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF09";
15637
+ lines3.push(` ${pad("WorkBuddy", 10)}${text}`);
14312
15638
  }
14313
15639
  for (const t of input.tools) {
14314
- if (t.notice) lines2.push(` ${t.notice}`);
15640
+ if (t.notice) lines3.push(` ${t.notice}`);
14315
15641
  }
14316
- lines2.push("");
15642
+ lines3.push("");
14317
15643
  const sorted = [...input.sessions].sort((a, b) => b.updatedAt - a.updatedAt);
14318
- lines2.push(`\u5F53\u524D\u672C\u5730\u4F1A\u8BDD\uFF08${sorted.length}\uFF09`);
15644
+ lines3.push(`\u5F53\u524D\u672C\u5730\u4F1A\u8BDD\uFF08${sorted.length}\uFF09`);
14319
15645
  if (sorted.length === 0) {
14320
- lines2.push(" \u6682\u65E0 \u2014\u2014 \u667A\u80FD\u4F53\u8DD1\u8D77\u6765\u4E4B\u540E\u8FD9\u91CC\u5C31\u4F1A\u6709\u5185\u5BB9");
15646
+ lines3.push(" \u6682\u65E0 \u2014\u2014 \u667A\u80FD\u4F53\u8DD1\u8D77\u6765\u4E4B\u540E\u8FD9\u91CC\u5C31\u4F1A\u6709\u5185\u5BB9");
14321
15647
  }
14322
15648
  for (const s2 of sorted) {
14323
- lines2.push(
15649
+ lines3.push(
14324
15650
  ` ${pad(s2.tool, 14)}${pad(s2.title || s2.project || "-", 24)}${pad(STATE_LABELS[s2.state] ?? s2.state, 8)}${relativeTime(s2.updatedAt, input.now)}`
14325
15651
  );
14326
15652
  }
14327
- lines2.push("");
14328
- lines2.push("\u91C7\u96C6\u8303\u56F4\uFF1A\u53EA\u4E0A\u62A5 \u5DE5\u5177\u540D/\u72B6\u6001/\u4F1A\u8BDD\u6807\u9898/\u9879\u76EE\u76EE\u5F55\u540D/\u65F6\u95F4\u6233");
14329
- lines2.push("\u4E0D\u4E0A\u62A5\uFF1Aprompt \u539F\u6587\u3001\u547D\u4EE4\u3001\u6587\u4EF6\u8DEF\u5F84\u3001\u4EE3\u7801\u5185\u5BB9");
14330
- return lines2.join("\n");
15653
+ lines3.push("");
15654
+ lines3.push("\u91C7\u96C6\u8303\u56F4\uFF1A\u53EA\u4E0A\u62A5 \u5DE5\u5177\u540D/\u72B6\u6001/\u4F1A\u8BDD\u6807\u9898/\u9879\u76EE\u76EE\u5F55\u540D/\u65F6\u95F4\u6233");
15655
+ lines3.push("\u4E0D\u4E0A\u62A5\uFF1Aprompt \u539F\u6587\u3001\u547D\u4EE4\u3001\u6587\u4EF6\u8DEF\u5F84\u3001\u4EE3\u7801\u5185\u5BB9");
15656
+ return lines3.join("\n");
14331
15657
  }
14332
15658
  function collectStatus(now = Date.now()) {
14333
15659
  const config = loadConfig();
@@ -14369,9 +15695,23 @@ function collectStatus(now = Date.now()) {
14369
15695
  winPending: process.platform === "win32" && !WIN_SHIM_ENABLED
14370
15696
  };
14371
15697
  }
15698
+ let workbuddyCli;
15699
+ if (config.enabledTools.includes("workbuddy")) {
15700
+ try {
15701
+ const s2 = locateWorkbuddyStandalone();
15702
+ workbuddyCli = {
15703
+ standalone: s2 ? [s2.file, ...s2.prefixArgs].join(" ") : null,
15704
+ app: s2 ? true : locateWorkbuddyApp() != null,
15705
+ declined: config.workbuddySetup?.declined === true
15706
+ };
15707
+ } catch {
15708
+ workbuddyCli = void 0;
15709
+ }
15710
+ }
14372
15711
  return {
14373
15712
  windowWrap,
14374
15713
  codexWrap,
15714
+ workbuddyCli,
14375
15715
  config,
14376
15716
  hookVersion: installedHookVersion(),
14377
15717
  currentVersion: packageVersion(),
@@ -14436,8 +15776,8 @@ async function runAutoSwitch(opts) {
14436
15776
 
14437
15777
  // src/wrap/run.ts
14438
15778
  import { randomBytes as randomBytes10 } from "crypto";
14439
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync29, statSync as statSync17, truncateSync as truncateSync3 } from "fs";
14440
- import { join as join41 } from "path";
15779
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync31, statSync as statSync20, truncateSync as truncateSync3 } from "fs";
15780
+ import { join as join45 } from "path";
14441
15781
  import { StringDecoder as StringDecoder2 } from "string_decoder";
14442
15782
 
14443
15783
  // src/wrap/injector.ts
@@ -14685,10 +16025,10 @@ var Injector = class {
14685
16025
  };
14686
16026
 
14687
16027
  // src/wrap/passthrough.ts
14688
- import { spawn as spawn6 } from "child_process";
16028
+ import { spawn as spawn7 } from "child_process";
14689
16029
  import { realpathSync as realpathSync7 } from "fs";
14690
16030
  import { constants as constants2 } from "os";
14691
- import { dirname as dirname20, resolve as resolve9 } from "path";
16031
+ import { dirname as dirname25, resolve as resolve9 } from "path";
14692
16032
  var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
14693
16033
  "agents",
14694
16034
  "attach",
@@ -14752,7 +16092,7 @@ function real(p) {
14752
16092
  function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
14753
16093
  const win = (d.platform ?? process.platform) === "win32";
14754
16094
  const skipKeys = /* @__PURE__ */ new Set([norm2(skip, win), norm2(real(skip), win)]);
14755
- const inSkip = (file) => skipKeys.has(norm2(dirname20(file), win)) || skipKeys.has(norm2(dirname20(real(file)), win));
16095
+ const inSkip = (file) => skipKeys.has(norm2(dirname25(file), win)) || skipKeys.has(norm2(dirname25(real(file)), win));
14756
16096
  const dd = { ...d, skipDirs: [...d.skipDirs ?? [], skip] };
14757
16097
  const name = SHIM_COMMANDS[tool];
14758
16098
  if (!name) {
@@ -14785,7 +16125,7 @@ function runDirect(real2, args, env = process.env) {
14785
16125
  return new Promise((done) => {
14786
16126
  let child;
14787
16127
  try {
14788
- child = spawn6(real2.file, [...real2.prefixArgs, ...args], {
16128
+ child = spawn7(real2.file, [...real2.prefixArgs, ...args], {
14789
16129
  stdio: "inherit",
14790
16130
  // 🔴 再经过垫片也直接放行,不会绕回来
14791
16131
  env: withoutShimFlag({ ...env, DESKBUDDY_WRAPPED: "1" }),
@@ -14835,11 +16175,11 @@ function runDirect(real2, args, env = process.env) {
14835
16175
  }
14836
16176
 
14837
16177
  // src/wrap/codexLaunch.ts
14838
- import { execFileSync as execFileSync16, spawn as spawn7 } from "child_process";
14839
- import { closeSync as closeSync8, mkdirSync as mkdirSync28, openSync as openSync8, readdirSync as readdirSync11, readFileSync as readFileSync27, rmSync as rmSync21, statSync as statSync16, truncateSync as truncateSync2, writeFileSync as writeFileSync27, renameSync as renameSync19 } from "fs";
16178
+ import { execFileSync as execFileSync16, spawn as spawn8 } from "child_process";
16179
+ import { closeSync as closeSync10, mkdirSync as mkdirSync30, openSync as openSync10, readdirSync as readdirSync12, readFileSync as readFileSync30, rmSync as rmSync23, statSync as statSync19, truncateSync as truncateSync2, writeFileSync as writeFileSync29, renameSync as renameSync20 } from "fs";
14840
16180
  import { request as request2 } from "http";
14841
16181
  import { createServer as createServer4 } from "net";
14842
- import { join as join39 } from "path";
16182
+ import { join as join43 } from "path";
14843
16183
  var TAKEOVER_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "--config", "-m", "--model", "-s", "--sandbox", "-a", "--ask-for-approval", "-C", "--cd", "--enable", "--disable"]);
14844
16184
  var SHORT_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "-m", "-s", "-a", "-C"]);
14845
16185
  var TAKEOVER_FLAGS = /* @__PURE__ */ new Set(["--search", "--no-alt-screen", "--dangerously-bypass-approvals-and-sandbox"]);
@@ -15008,12 +16348,12 @@ async function runCodexWindow(real2, args, d) {
15008
16348
  }
15009
16349
  var MAX_LOG_BYTES2 = 1024 * 1024;
15010
16350
  function appServerLogPath() {
15011
- return join39(deskbuddyHome(), "codex-app-server.log");
16351
+ return join43(deskbuddyHome(), "codex-app-server.log");
15012
16352
  }
15013
16353
  function windowsDir2() {
15014
- return join39(deskbuddyHome(), "codex-windows");
16354
+ return join43(deskbuddyHome(), "codex-windows");
15015
16355
  }
15016
- function pidAlive5(pid) {
16356
+ function pidAlive6(pid) {
15017
16357
  try {
15018
16358
  process.kill(pid, 0);
15019
16359
  return true;
@@ -15024,11 +16364,11 @@ function pidAlive5(pid) {
15024
16364
  function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
15025
16365
  let pid = 0;
15026
16366
  try {
15027
- pid = Number(readFileSync27(pidFile, "utf8").trim());
16367
+ pid = Number(readFileSync30(pidFile, "utf8").trim());
15028
16368
  } catch {
15029
16369
  return false;
15030
16370
  }
15031
- if (!Number.isInteger(pid) || pid <= 0 || !pidAlive5(pid)) return false;
16371
+ if (!Number.isInteger(pid) || pid <= 0 || !pidAlive6(pid)) return false;
15032
16372
  const a = args(pid);
15033
16373
  return !!a && /cli\.js daemon(?:\s|$)/.test(a);
15034
16374
  }
@@ -15061,22 +16401,22 @@ function serverSpawnOptions(platform, fd, cwd, env) {
15061
16401
  }
15062
16402
  function startServer(real2, url, env, cwd, platform = process.platform) {
15063
16403
  const logPath2 = appServerLogPath();
15064
- mkdirSync28(deskbuddyHome(), { recursive: true });
16404
+ mkdirSync30(deskbuddyHome(), { recursive: true });
15065
16405
  try {
15066
- if (statSync16(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
16406
+ if (statSync19(logPath2).size > MAX_LOG_BYTES2) truncateSync2(logPath2, 0);
15067
16407
  } catch {
15068
16408
  }
15069
- const fd = openSync8(logPath2, "a");
16409
+ const fd = openSync10(logPath2, "a");
15070
16410
  try {
15071
16411
  const childEnv2 = { ...env, DESKBUDDY_WRAPPED: "1" };
15072
16412
  delete childEnv2.DESKBUDDY_SHIM;
15073
- const child = spawn7(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], serverSpawnOptions(platform, fd, cwd, childEnv2));
16413
+ const child = spawn8(real2.file, [...real2.prefixArgs, "app-server", "--listen", url], serverSpawnOptions(platform, fd, cwd, childEnv2));
15074
16414
  child.on("error", () => {
15075
16415
  });
15076
16416
  child.unref();
15077
16417
  return child.pid ?? null;
15078
16418
  } finally {
15079
- closeSync8(fd);
16419
+ closeSync10(fd);
15080
16420
  }
15081
16421
  }
15082
16422
  function killServer(pid, platform = process.platform, run2 = taskkill) {
@@ -15101,41 +16441,41 @@ function killOwnServer(pid, url, args = freshArgs, kill = (p) => killServer(p))
15101
16441
  if (a && isAppServerArgs(a, url)) kill(pid);
15102
16442
  }
15103
16443
  function recordPath(rec) {
15104
- return join39(windowsDir2(), `${rec.server}.json`);
16444
+ return join43(windowsDir2(), `${rec.server}.json`);
15105
16445
  }
15106
16446
  function writeRecord(rec) {
15107
16447
  try {
15108
- mkdirSync28(windowsDir2(), { recursive: true, mode: 448 });
16448
+ mkdirSync30(windowsDir2(), { recursive: true, mode: 448 });
15109
16449
  const p = recordPath(rec);
15110
- writeFileSync27(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
15111
- renameSync19(p + ".tmp", p);
16450
+ writeFileSync29(p + ".tmp", JSON.stringify(rec), { encoding: "utf8", mode: 384 });
16451
+ renameSync20(p + ".tmp", p);
15112
16452
  } catch {
15113
16453
  }
15114
16454
  }
15115
16455
  function removeRecord(rec) {
15116
16456
  try {
15117
- rmSync21(recordPath(rec), { force: true });
16457
+ rmSync23(recordPath(rec), { force: true });
15118
16458
  } catch {
15119
16459
  }
15120
16460
  }
15121
- function reapWindows(dir = windowsDir2(), alive = pidAlive5, args = freshArgs, kill = (pid) => killServer(pid)) {
16461
+ function reapWindows(dir = windowsDir2(), alive = pidAlive6, args = freshArgs, kill = (pid) => killServer(pid)) {
15122
16462
  let names = [];
15123
16463
  try {
15124
- names = readdirSync11(dir).filter((n) => n.endsWith(".json"));
16464
+ names = readdirSync12(dir).filter((n) => n.endsWith(".json"));
15125
16465
  } catch {
15126
16466
  return;
15127
16467
  }
15128
16468
  for (const n of names) {
15129
- const p = join39(dir, n);
16469
+ const p = join43(dir, n);
15130
16470
  let rec;
15131
16471
  try {
15132
- rec = JSON.parse(readFileSync27(p, "utf8"));
16472
+ rec = JSON.parse(readFileSync30(p, "utf8"));
15133
16473
  } catch {
15134
- rmSync21(p, { force: true });
16474
+ rmSync23(p, { force: true });
15135
16475
  continue;
15136
16476
  }
15137
16477
  if (typeof rec.supervisor !== "number" || typeof rec.server !== "number" || typeof rec.url !== "string") {
15138
- rmSync21(p, { force: true });
16478
+ rmSync23(p, { force: true });
15139
16479
  continue;
15140
16480
  }
15141
16481
  if (alive(rec.supervisor)) continue;
@@ -15143,13 +16483,13 @@ function reapWindows(dir = windowsDir2(), alive = pidAlive5, args = freshArgs, k
15143
16483
  const a = args(rec.server);
15144
16484
  if (a && isAppServerArgs(a, rec.url)) kill(rec.server);
15145
16485
  }
15146
- rmSync21(p, { force: true });
16486
+ rmSync23(p, { force: true });
15147
16487
  }
15148
16488
  }
15149
16489
  function launchLog(line) {
15150
16490
  try {
15151
- mkdirSync28(deskbuddyHome(), { recursive: true });
15152
- writeFileSync27(join39(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
16491
+ mkdirSync30(deskbuddyHome(), { recursive: true });
16492
+ writeFileSync29(join43(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
15153
16493
  `, { flag: "a" });
15154
16494
  } catch {
15155
16495
  }
@@ -15172,9 +16512,9 @@ function defaultCodexWindowDeps(base) {
15172
16512
  }
15173
16513
 
15174
16514
  // src/wrap/promptLaunch.ts
15175
- import { randomBytes as randomBytes8 } from "crypto";
15176
- import { homedir as homedir12 } from "os";
15177
- import { join as join40 } from "path";
16515
+ import { randomBytes as randomBytes8, randomUUID as randomUUID4 } from "crypto";
16516
+ import { homedir as homedir13 } from "os";
16517
+ import { join as join44 } from "path";
15178
16518
  var FIRST_LINE_WAIT_MS2 = 2 * 60 * 1e3;
15179
16519
  function promptArgs(text) {
15180
16520
  return text ? ["--", prepare(text)] : [];
@@ -15182,7 +16522,7 @@ function promptArgs(text) {
15182
16522
  function promptEnv(tool, env, launcherPid = process.pid, answerWindowId = "") {
15183
16523
  const out = { ...env };
15184
16524
  if (tool === "workbuddy") {
15185
- if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR = join40(homedir12(), ".workbuddy");
16525
+ if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR = join44(homedir13(), ".workbuddy");
15186
16526
  out.DESKBUDDY_LAUNCHER_PID = String(launcherPid);
15187
16527
  out.DESKBUDDY_LAUNCHED_TOOL = tool;
15188
16528
  if (answerWindowId) out.DESKBUDDY_LAUNCHED_WINDOW = answerWindowId;
@@ -15199,8 +16539,10 @@ function workbuddyNotifyArgs(hookPath, platform = process.platform) {
15199
16539
  };
15200
16540
  return ["--settings", JSON.stringify(settings)];
15201
16541
  }
15202
- function launchArgs(tool, platform, hookPath = installedHookPath()) {
15203
- return tool === "workbuddy" ? workbuddyNotifyArgs(hookPath, platform) : [];
16542
+ function launchArgs(tool, platform, hookPath = installedHookPath(), sessionId = "") {
16543
+ if (tool === "workbuddy") return workbuddyNotifyArgs(hookPath, platform);
16544
+ if (tool === "qoder_cn" && sessionId) return ["--session-id", sessionId];
16545
+ return [];
15204
16546
  }
15205
16547
  async function runPromptLaunch(tool, windowId, cwd, d) {
15206
16548
  let real2 = null;
@@ -15234,7 +16576,20 @@ async function runPromptLaunch(tool, windowId, cwd, d) {
15234
16576
  d.log(`[${windowId.slice(0, 8)}] \u6CA1\u7B49\u5230\u9996\u53E5\uFF0C\u8D77\u4E00\u4E2A\u7A7A\u7684 ${tool}`);
15235
16577
  }
15236
16578
  const answerWindowId = tool === "workbuddy" || tool === "codex" ? randomBytes8(16).toString("hex") : "";
15237
- return d.run(tool, real2, [...launchArgs(tool, d.platform), ...promptArgs(text)], promptEnv(tool, d.env, process.pid, answerWindowId), answerWindowId);
16579
+ if (tool !== "qoder_cn") {
16580
+ return d.run(tool, real2, [...launchArgs(tool, d.platform), ...promptArgs(text)], promptEnv(tool, d.env, process.pid, answerWindowId), answerWindowId);
16581
+ }
16582
+ const sid = (d.newSessionId ?? randomUUID4)();
16583
+ const env = { ...promptEnv(tool, d.env), ...real2.env ?? {} };
16584
+ const code = await d.run(tool, real2, [...launchArgs(tool, d.platform, "", sid), ...promptArgs(text)], env);
16585
+ if (d.afterQoder) {
16586
+ try {
16587
+ await d.afterQoder(sid, cwd, text);
16588
+ } catch (e) {
16589
+ d.log(`\u767B\u8BB0 Qoder \u4F1A\u8BDD\u51FA\u9519\uFF1A${e.message}`);
16590
+ }
16591
+ }
16592
+ return code;
15238
16593
  }
15239
16594
  function receiveFirstLine(windowId, tool, cwd, opts = {}) {
15240
16595
  const platform = opts.platform ?? process.platform;
@@ -15321,18 +16676,18 @@ import { StringDecoder } from "string_decoder";
15321
16676
  var TITLE = /^\s*Select login method:\s*$/;
15322
16677
  var OPTION = /^\s*(?:[❯>›]\s*)?Log in via (?:Chinese Site|International Site|Enterprise Domain|iOA\b)/;
15323
16678
  var HINT = /^\s*\(Use ↑↓ to select, Enter to login\)\s*$/;
15324
- function isLoginPage(lines2) {
16679
+ function isLoginPage(lines3) {
15325
16680
  let title = -1;
15326
- for (let i = lines2.length - 1; i >= 0; i--) {
15327
- if (TITLE.test(lines2[i])) {
16681
+ for (let i = lines3.length - 1; i >= 0; i--) {
16682
+ if (TITLE.test(lines3[i])) {
15328
16683
  title = i;
15329
16684
  break;
15330
16685
  }
15331
16686
  }
15332
16687
  if (title < 0) return false;
15333
- const below = lines2.slice(title + 1);
16688
+ const below = lines3.slice(title + 1);
15334
16689
  const options = below.filter((l) => OPTION.test(l)).length;
15335
- const tail2 = lines2.filter((l) => l.trim() !== "").slice(-HINT_TAIL_LINES);
16690
+ const tail2 = lines3.filter((l) => l.trim() !== "").slice(-HINT_TAIL_LINES);
15336
16691
  return options >= 2 && tail2.some((l) => HINT.test(l));
15337
16692
  }
15338
16693
  var HINT_TAIL_LINES = 3;
@@ -15349,8 +16704,8 @@ var LoginWatcher = class {
15349
16704
  get onScreen() {
15350
16705
  return this.#onScreen;
15351
16706
  }
15352
- tick(lines2) {
15353
- this.#onScreen = !!lines2 && isLoginPage(lines2);
16707
+ tick(lines3) {
16708
+ this.#onScreen = !!lines3 && isLoginPage(lines3);
15354
16709
  if (this.#reported) return;
15355
16710
  if (!this.#onScreen) {
15356
16711
  this.#since = null;
@@ -15488,9 +16843,9 @@ async function runAnswerWindow(tool, real2, args, env, windowId, d) {
15488
16843
  }) : null;
15489
16844
  const trustTimer = setInterval(() => {
15490
16845
  try {
15491
- const lines2 = term && !finished ? screenLines(term) : null;
15492
- trust.tick(lines2);
15493
- login?.tick(lines2);
16846
+ const lines3 = term && !finished ? screenLines(term) : null;
16847
+ trust.tick(lines3);
16848
+ login?.tick(lines3);
15494
16849
  } catch {
15495
16850
  }
15496
16851
  }, TRUST_TICK_MS);
@@ -15613,10 +16968,10 @@ function childEnv(src, windowId) {
15613
16968
  }
15614
16969
  function wrapLog(line) {
15615
16970
  try {
15616
- const p = join41(deskbuddyHome(), "wrap.log");
15617
- mkdirSync29(deskbuddyHome(), { recursive: true });
16971
+ const p = join45(deskbuddyHome(), "wrap.log");
16972
+ mkdirSync31(deskbuddyHome(), { recursive: true });
15618
16973
  try {
15619
- if (statSync17(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
16974
+ if (statSync20(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
15620
16975
  } catch {
15621
16976
  }
15622
16977
  appendFileSync2(p, `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${line}
@@ -15720,7 +17075,8 @@ function defaultPromptDeps(deps2, cwd, launchId) {
15720
17075
  now: () => Date.now(),
15721
17076
  stderr: deps2.stderr,
15722
17077
  // WorkBuddy 开窗:要有交互界面的那份 CLI(app 自带的可能没有,退到 PATH 上的,见 locateWorkbuddyTui)
15723
- findReal: (tool) => tool === "workbuddy" ? locateWorkbuddyTui() : deps2.findReal(tool),
17078
+ // Qoder CN 开窗:PATH 上的 qoderclicn 优先(不带 ELECTRON_RUN_AS_NODE),没有才用 worker(见 locateQoderCnTui)
17079
+ findReal: (tool) => tool === "workbuddy" ? locateWorkbuddyTui() : tool === "qoder_cn" ? locateQoderCnTui() : deps2.findReal(tool),
15724
17080
  receive: (id, tool, c) => receiveFirstLine(id, tool, c, { platform: deps2.platform, log: deps2.log }),
15725
17081
  run: async (tool, real2, args, env, answerWindowId) => {
15726
17082
  if (tool === "workbuddy" && answerWindowId) {
@@ -15754,7 +17110,11 @@ function defaultPromptDeps(deps2, cwd, launchId) {
15754
17110
  return runDirect(real2, args, env);
15755
17111
  },
15756
17112
  env: deps2.env,
15757
- log: deps2.log
17113
+ log: deps2.log,
17114
+ afterQoder: async (sid, c, first) => {
17115
+ const r = await syncQoderApp({ tool: "qoder_cn", kind: "register", sessionId: sid, cwd: c, fallbackTitle: first ?? "" });
17116
+ deps2.log(`[${(launchId ?? "").slice(0, 8)}] Qoder \u4F1A\u8BDD ${sid.slice(0, 8)} \u767B\u8BB0\u8FDB\u684C\u9762\u7AEF\u5217\u8868\uFF1A${r}`);
17117
+ }
15758
17118
  };
15759
17119
  }
15760
17120
  async function runLaunch(argv, deps2 = defaultWrapDeps(), ld = defaultLaunchDeps()) {
@@ -16076,6 +17436,46 @@ ${WRAP_HINT}\uFF08\u8BBE\u5907\u56DE\u590D\u6682\u65F6\u5199\u4E0D\u8FDB\u8FD9\u
16076
17436
  });
16077
17437
  }
16078
17438
 
17439
+ // src/commands/workbuddySetup.ts
17440
+ function parseFrom(argv) {
17441
+ const i = argv.indexOf("--from");
17442
+ const v = i >= 0 ? argv[i + 1] : void 0;
17443
+ return v === "install" || v === "enable" ? v : null;
17444
+ }
17445
+ async function runWorkbuddySetup(opts = {}) {
17446
+ const out = opts.out ?? ((l) => process.stdout.write(l + "\n"));
17447
+ if (opts.from) {
17448
+ const d2 = opts.deps ?? defaultOfferDeps(log);
17449
+ await runOffer(opts.from, d2);
17450
+ return 0;
17451
+ }
17452
+ const base = opts.deps ?? defaultOfferDeps(log);
17453
+ const d = { ...base, log: (l) => (out(l), base.log(l)) };
17454
+ const cur = opts.current ? opts.current() : (() => {
17455
+ const w = locateWorkbuddy();
17456
+ return w ? { file: [w.loc.file, ...w.loc.prefixArgs].join(" "), kind: w.kind } : null;
17457
+ })();
17458
+ if (cur?.kind === "standalone") {
17459
+ out(`\u5DF2\u5B89\u88C5 WorkBuddy \u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF1A${cur.file}`);
17460
+ out(`\u6CA1\u767B\u5F55\u7684\u8BDD\u5728\u7EC8\u7AEF\u8FD0\u884C ${manualLoginCommand(d.platform)}\uFF0C\u8F93\u5165 /login \u9009\u56FD\u5185\u7248`);
17461
+ return 0;
17462
+ }
17463
+ const r = await runOffer("manual", d, { skipAsk: !!opts.yes });
17464
+ switch (r) {
17465
+ case "installed":
17466
+ return 0;
17467
+ case "no_gui":
17468
+ out(`\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\uFF0C\u6CA1\u5F39\u6846\u3002\u53EF\u4EE5\u52A0 --yes \u76F4\u63A5\u88C5\uFF1Adeskbuddy workbuddy-setup --yes\uFF1B\u6216\u8005${manualHint(d.platform).replace(/^请/, "")}`);
17469
+ return 0;
17470
+ case "later":
17471
+ case "timeout":
17472
+ out(`\u6CA1\u88C5\u3002\u4E0D\u88C5\u7684\u8BDD${LIMITED_NOTE}\uFF1B\u4E4B\u540E\u8FD0\u884C deskbuddy workbuddy-setup \u53EF\u518D\u88C5`);
17473
+ return 0;
17474
+ default:
17475
+ return 1;
17476
+ }
17477
+ }
17478
+
16079
17479
  // src/cli.ts
16080
17480
  var USAGE = `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
16081
17481
 
@@ -16091,6 +17491,8 @@ var USAGE = `deskbuddy \u2014\u2014 \u628A AI \u7F16\u7801\u667A\u80FD\u4F53\u6B
16091
17491
  deskbuddy daemon [--foreground] \u5E38\u9A7B\u6536\u53D6\u8BBE\u5907\u4E0B\u53D1\u7684\u6307\u4EE4\uFF08\u5F00\u673A\u81EA\u542F\u4F1A\u81EA\u52A8\u62C9\u8D77\uFF09
16092
17492
  deskbuddy status \u770B\u91C7\u96C6\u5230\u4E86\u4EC0\u4E48\u3001\u4E0A\u62A5\u4E86\u54EA\u4E9B\u5B57\u6BB5
16093
17493
  deskbuddy logout [--yes] \u79FB\u9664 hook \u5E76\u6E05\u9664\u672C\u673A\u6570\u636E
17494
+ deskbuddy workbuddy-setup [--yes] \u5B89\u88C5 WorkBuddy \u7684\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08CodeBuddy Code\uFF09\uFF0C\u88C5\u4E86\u624D\u80FD\u5728
17495
+ \u8BBE\u5907\u4E0A\u65B0\u5EFA / \u56DE\u590D WorkBuddy \u4EFB\u52A1\uFF08--yes \u4E0D\u5F39\u6846\u76F4\u63A5\u88C5\uFF09
16094
17496
  deskbuddy wrap <tool> [\u53C2\u6570\u2026] \uFF08\u57AB\u7247\u5185\u90E8\u7528\uFF09\u7ECF DeskBuddy \u6253\u5F00\u667A\u80FD\u4F53\u7A97\u53E3\uFF0C
16095
17497
  \u8BBE\u5907\u4E0A\u7684\u56DE\u590D\u80FD\u5199\u8FDB\u8FD9\u4E2A\u7A97\u53E3\uFF1BDESKBUDDY_WRAP_OFF=1 \u53EF\u7ED5\u5F00
16096
17498
 
@@ -16135,6 +17537,8 @@ async function main(argv) {
16135
17537
  return runStatus();
16136
17538
  case "logout":
16137
17539
  return runLogout({ yes: argv.includes("--yes") || argv.includes("-y") });
17540
+ case "workbuddy-setup":
17541
+ return runWorkbuddySetup({ yes: argv.includes("--yes") || argv.includes("-y"), from: parseFrom(argv) });
16138
17542
  case "wrap":
16139
17543
  return runWrap(argv.slice(3));
16140
17544
  default: