deskbuddy 0.2.0-beta.30 → 0.2.0-beta.32

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands/enable.ts
4
- import { dirname as dirname19, join as join35 } from "path";
4
+ import { dirname as dirname19, join as join36 } from "path";
5
5
 
6
6
  // src/core/config.ts
7
7
  import { randomUUID } from "crypto";
@@ -67,6 +67,9 @@ function qoderCnSettingsPath() {
67
67
  function qwenworkHome() {
68
68
  return process.env.DESKBUDDY_QWENWORK_DIR || join(homedir(), ".QwenWorkCN");
69
69
  }
70
+ function qwenworkDataHome() {
71
+ return process.env.DESKBUDDY_QWENWORK_CN_HOME || join(homedir(), ".qwenworkcn");
72
+ }
70
73
  function qwenworkSettingsPath() {
71
74
  return join(qwenworkHome(), "settings.json");
72
75
  }
@@ -1523,6 +1526,19 @@ function installCcWindowGate(settingsPath, gatePath, tool = "claude_code", platf
1523
1526
  });
1524
1527
  writeSettings(settingsPath, settings);
1525
1528
  }
1529
+ var PRETOOL_QUESTION_TIMEOUT_S = 660;
1530
+ var PRETOOL_QUESTION_MATCHER = "^AskUserQuestion$";
1531
+ function installCcPreToolQuestion(settingsPath, gatePath, tool, platform = process.platform) {
1532
+ const settings = readSettings(settingsPath);
1533
+ settings.hooks ??= {};
1534
+ const groups = settings.hooks.PreToolUse ??= [];
1535
+ if (groups.some((g) => g.hooks?.some((h) => mentions(h.command, gatePath) && h.command.includes("--pretool-question")))) return;
1536
+ groups.push({
1537
+ matcher: PRETOOL_QUESTION_MATCHER,
1538
+ hooks: [{ type: "command", command: `node ${scriptArg(gatePath, platform, shQuote2)} --pretool-question --tool ${tool}`, timeout: PRETOOL_QUESTION_TIMEOUT_S }]
1539
+ });
1540
+ writeSettings(settingsPath, settings);
1541
+ }
1526
1542
  var REWAKE_TIMEOUT_S = 20 * 24 * 60 * 60;
1527
1543
  var REWAKE_MESSAGE = "\u4EE5\u4E0B\u662F\u4E00\u6761 DeskBuddy \u8BBE\u5907\u6307\u4EE4\uFF08\u6309\u4F1A\u8BDD\u5F00\u5934 DeskBuddy \u8BF4\u660E\u91CC\u7684\u53E3\u4EE4\u6838\u5BF9\uFF0C\u53E3\u4EE4\u4E00\u81F4\u624D\u7167\u5E38\u6267\u884C\uFF0C\u5B8C\u6210\u540E\u7B80\u8981\u56DE\u590D\u7ED3\u679C\uFF09\uFF1A";
1528
1544
  var REWAKE_SUMMARY = "DeskBuddy \u8BBE\u5907\u6307\u4EE4";
@@ -1871,12 +1887,12 @@ function isDshPluginInstalled(patchPath) {
1871
1887
  var DSH_NOTICE = " \u5DF2\u5F00\u7740\u7684 DeepSeek Harness\uFF08dsh web / \u684C\u9762\u7AEF\uFF09\u91CD\u542F\u540E\u751F\u6548";
1872
1888
 
1873
1889
  // src/daemon/dumateApp/shortcuts.ts
1874
- import { join as join25 } from "path";
1890
+ import { join as join26 } from "path";
1875
1891
 
1876
1892
  // src/daemon/appShortcuts.ts
1877
1893
  import { execFile as execFile2, execFileSync as execFileSync4 } from "child_process";
1878
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync12, renameSync as renameSync8, writeFileSync as writeFileSync11 } from "fs";
1879
- import { join as join10, win32 } from "path";
1894
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync13, renameSync as renameSync8, writeFileSync as writeFileSync11 } from "fs";
1895
+ import { join as join10, win32 as win322 } from "path";
1880
1896
 
1881
1897
  // src/core/fileLock.ts
1882
1898
  import { existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync11, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
@@ -1937,7 +1953,73 @@ function pidAlive2(pid) {
1937
1953
  }
1938
1954
  }
1939
1955
 
1956
+ // src/daemon/winPaths.ts
1957
+ import { readFileSync as readFileSync12, statSync as statSync4 } from "fs";
1958
+ import { win32 } from "path";
1959
+ function decodeText(b) {
1960
+ if (b.length >= 2 && b[0] === 255 && b[1] === 254) return b.subarray(2).toString("utf16le");
1961
+ if (b.length >= 2 && b[0] === 254 && b[1] === 255) {
1962
+ const be = Buffer.from(b.subarray(2, 2 + (b.length - 2 & ~1)));
1963
+ return be.swap16().toString("utf16le");
1964
+ }
1965
+ if (b.length >= 3 && b[0] === 239 && b[1] === 187 && b[2] === 191) return b.subarray(3).toString("utf8");
1966
+ return b.toString("utf8");
1967
+ }
1968
+ function readSmallText(p) {
1969
+ try {
1970
+ if (statSync4(p).size > 64 * 1024) return null;
1971
+ return decodeText(readFileSync12(p));
1972
+ } catch {
1973
+ return null;
1974
+ }
1975
+ }
1976
+ function isValidWinPath(p) {
1977
+ if (typeof p !== "string" || p.length < 4 || p.length > 1024) return false;
1978
+ if (!/^[a-z]:\\/i.test(p)) return false;
1979
+ if (/[\u0000-\u001f*?"<>|%]/.test(p)) return false;
1980
+ return !p.split("\\").includes("..");
1981
+ }
1982
+ function normalizeHint(raw) {
1983
+ if (typeof raw !== "string") return null;
1984
+ let v = raw.trim();
1985
+ if (!v) return null;
1986
+ if (v.startsWith('"')) {
1987
+ const end = v.indexOf('"', 1);
1988
+ if (end < 0) return null;
1989
+ v = v.slice(1, end);
1990
+ } else {
1991
+ v = v.replace(/,\s*-?\d+$/, "");
1992
+ const m = /^(.*?\.exe)(\s|$)/i.exec(v);
1993
+ if (m) v = m[1];
1994
+ }
1995
+ v = v.replace(/\//g, "\\").replace(/\\+$/, "").trim();
1996
+ return isValidWinPath(v) ? v : null;
1997
+ }
1998
+ function launcherTarget(text, exe) {
1999
+ const kv = /* @__PURE__ */ new Map();
2000
+ for (const line of text.split(/\r?\n/)) {
2001
+ const i = line.indexOf("=");
2002
+ if (i > 0) kv.set(line.slice(0, i).trim(), line.slice(i + 1).trim());
2003
+ }
2004
+ const dir2 = (kv.get("installDir") ?? "").replace(/\//g, "\\").replace(/\\+$/, "");
2005
+ const app = (kv.get("appExecutable") ?? "").replace(/\//g, "\\");
2006
+ const seg = app.split("\\");
2007
+ const shapeOk = seg.length === 1 || seg.length === 3 && seg[0].toLowerCase() === ".qoder-versions" && /^[0-9a-z][0-9a-z._-]*$/i.test(seg[1]);
2008
+ if (!shapeOk || seg[seg.length - 1].toLowerCase() !== exe.toLowerCase() || !isValidWinPath(dir2)) return null;
2009
+ const p = win32.join(dir2, app);
2010
+ return isValidWinPath(p) ? p : null;
2011
+ }
2012
+
1940
2013
  // src/daemon/appShortcuts.ts
2014
+ function exeNameOf(app) {
2015
+ let p = null;
2016
+ try {
2017
+ p = app.locatedExe?.() ?? null;
2018
+ } catch {
2019
+ p = null;
2020
+ }
2021
+ return p ? win322.basename(p) : app.exe;
2022
+ }
1941
2023
  var LNK_LIST_PS = String.raw`[Console]::OutputEncoding=[Text.Encoding]::UTF8
1942
2024
  $s = New-Object -ComObject WScript.Shell
1943
2025
  $dirs = @(@([Environment]::GetFolderPath('Programs'), $true), @([Environment]::GetFolderPath('CommonPrograms'), $true), @([Environment]::GetFolderPath('Desktop'), $false), @([Environment]::GetFolderPath('CommonDesktopDirectory'), $false), @((Join-Path $env:APPDATA 'Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar'), $false))
@@ -1951,13 +2033,16 @@ foreach ($d in $dirs) {
1951
2033
  ConvertTo-Json -InputObject @($out) -Compress`;
1952
2034
  var LNK_WRITE_PS = String.raw`[Console]::OutputEncoding=[Text.Encoding]::UTF8
1953
2035
  $ops = $env:DB_LNK_OPS | ConvertFrom-Json
2036
+ # 允许的 Launcher 目标(完整路径,小写);PowerShell 5.1 的 ConvertFrom-Json 数组要展开
2037
+ $launchers = @($env:DB_LNK_LAUNCHERS | ConvertFrom-Json | ForEach-Object { $_ } | ForEach-Object { ([string]$_).ToLowerInvariant() })
1954
2038
  $s = New-Object -ComObject WScript.Shell
1955
2039
  $out = @()
1956
2040
  foreach ($o in @($ops)) {
1957
2041
  $ok = $false
1958
2042
  try {
1959
2043
  $l = $s.CreateShortcut([string]$o.path)
1960
- if ([IO.Path]::GetFileName([string]$l.TargetPath) -ieq [string]$env:DB_LNK_EXE) { $l.Arguments = [string]$o.args; $l.Save(); $ok = $true }
2044
+ $t = [string]$l.TargetPath
2045
+ if (([IO.Path]::GetFileName($t) -ieq [string]$env:DB_LNK_EXE) -or ($launchers -contains $t.ToLowerInvariant())) { $l.Arguments = [string]$o.args; $l.Save(); $ok = $true }
1961
2046
  } catch {}
1962
2047
  $out += [pscustomobject]@{ path = [string]$o.path; ok = $ok }
1963
2048
  }
@@ -1969,7 +2054,42 @@ function hasPortFlag(args) {
1969
2054
  return /(^|\s)--remote-debugging-port(=|\s|$)/.test(args);
1970
2055
  }
1971
2056
  function isAppLnk(l, exe) {
1972
- return win32.basename(l.target || "").toLowerCase() === exe.toLowerCase();
2057
+ if (typeof exe === "function") return exe(l);
2058
+ return win322.basename(l.target || "").toLowerCase() === exe.toLowerCase();
2059
+ }
2060
+ function lnkMatcher(app, readText = readSmall, restore = false) {
2061
+ const exe = exeNameOf(app);
2062
+ let located = null;
2063
+ try {
2064
+ located = app.locatedExe?.() ?? null;
2065
+ } catch {
2066
+ located = null;
2067
+ }
2068
+ const cache2 = /* @__PURE__ */ new Map();
2069
+ return (l) => {
2070
+ const target = l.target || "";
2071
+ if (win322.basename(target).toLowerCase() === exe.toLowerCase()) return true;
2072
+ if (!located && !restore || !isValidWinPath(target) || !/\.exe$/i.test(target)) return false;
2073
+ const k = target.toLowerCase();
2074
+ if (!cache2.has(k)) {
2075
+ const ini = readText(win322.join(win322.dirname(target), "install.ini"));
2076
+ const to = ini ? launcherTarget(ini, exe) : null;
2077
+ cache2.set(k, !!to && (restore || !!located && to.toLowerCase() === located.toLowerCase()));
2078
+ }
2079
+ return cache2.get(k) === true;
2080
+ };
2081
+ }
2082
+ function launcherTargets(list, writes, exe) {
2083
+ const byPath = new Map(list.map((l) => [l.path.toLowerCase(), l.target]));
2084
+ const out = /* @__PURE__ */ new Set();
2085
+ for (const w of writes) {
2086
+ const t = byPath.get(w.path.toLowerCase());
2087
+ if (t && win322.basename(t).toLowerCase() !== exe.toLowerCase()) out.add(t.toLowerCase());
2088
+ }
2089
+ return [...out];
2090
+ }
2091
+ function readSmall(p) {
2092
+ return readSmallText(p);
1973
2093
  }
1974
2094
  var keyOf = (p) => p.toLowerCase();
1975
2095
  function planPatchFor(exe, list, records, port) {
@@ -2001,7 +2121,7 @@ function recordsFile(app) {
2001
2121
  }
2002
2122
  function loadRecords(file3) {
2003
2123
  try {
2004
- const v = JSON.parse(readFileSync12(file3, "utf8"));
2124
+ const v = JSON.parse(readFileSync13(file3, "utf8"));
2005
2125
  if (!v || typeof v !== "object") return {};
2006
2126
  const out = {};
2007
2127
  for (const [k, r] of Object.entries(v)) {
@@ -2030,9 +2150,9 @@ function parseJsonArray(out) {
2030
2150
  }
2031
2151
  }
2032
2152
  function parseList(out) {
2033
- const arr = parseJsonArray(out);
2034
- if (!arr) return null;
2035
- return arr.filter((x) => !!x && typeof x.path === "string" && typeof x.target === "string").map((x) => ({ path: x.path, target: x.target, args: typeof x.args === "string" ? x.args : "" }));
2153
+ const arr2 = parseJsonArray(out);
2154
+ if (!arr2) return null;
2155
+ return arr2.filter((x) => !!x && typeof x.path === "string" && typeof x.target === "string").map((x) => ({ path: x.path, target: x.target, args: typeof x.args === "string" ? x.args : "" }));
2036
2156
  }
2037
2157
  var psArgs = (script) => ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script];
2038
2158
  var defaultPs = (script, env) => new Promise((done) => {
@@ -2062,10 +2182,15 @@ var nowOf = (d) => d.now ? d.now() : Date.now();
2062
2182
  var fileOf = (app, d) => d.file ?? recordsFile(app);
2063
2183
  var failFileOf = (app, d) => d.failFile ?? join10(deskbuddyHome(), app.dir, `${app.stem}-fail.json`);
2064
2184
  var lockFileOf = (app, d) => d.lockFile ?? join10(deskbuddyHome(), app.dir, `${app.stem}.lock`);
2065
- var writeEnv = (app, ops) => ({ DB_LNK_OPS: JSON.stringify(ops), DB_LNK_EXE: app.exe });
2185
+ var writeEnv = (app, ops, list) => ({
2186
+ DB_LNK_OPS: JSON.stringify(ops),
2187
+ DB_LNK_EXE: exeNameOf(app),
2188
+ DB_LNK_LAUNCHERS: JSON.stringify(launcherTargets(list, ops, exeNameOf(app)))
2189
+ });
2190
+ var matcherOf = (app, d, restore = false) => lnkMatcher(app, d.readText, restore);
2066
2191
  function loadFails(app, d) {
2067
2192
  try {
2068
- const v = JSON.parse(readFileSync12(failFileOf(app, d), "utf8"));
2193
+ const v = JSON.parse(readFileSync13(failFileOf(app, d), "utf8"));
2069
2194
  const out = {};
2070
2195
  for (const [k, t] of Object.entries(v ?? {})) if (typeof t === "number") out[k] = t;
2071
2196
  return out;
@@ -2116,7 +2241,7 @@ function applyResults(app, d, writes, results, records, prev) {
2116
2241
  return out;
2117
2242
  }
2118
2243
  function restoreWith(app, d, list, records, write3) {
2119
- const writes = planRestoreFor(app.exe, list, records, portOf(app, d));
2244
+ const writes = planRestoreFor(matcherOf(app, d, true), list, records, portOf(app, d));
2120
2245
  const finish = (results) => {
2121
2246
  const ok = new Set((results ?? []).filter((r2) => r2 && r2.ok === true && typeof r2.path === "string").map((r2) => keyOf(r2.path)));
2122
2247
  const keep = {};
@@ -2146,7 +2271,7 @@ async function ensureAppShortcuts(app, d, ps = defaultPs) {
2146
2271
  const prev = loadRecords(fileOf(app, d));
2147
2272
  const fails = loadFails(app, d);
2148
2273
  const now = nowOf(d);
2149
- const plan = planPatchFor(app.exe, list, prev, portOf(app, d));
2274
+ const plan = planPatchFor(matcherOf(app, d), list, prev, portOf(app, d));
2150
2275
  const writes = plan.writes.filter((w) => {
2151
2276
  const t = fails[keyOf(w.path)];
2152
2277
  return !t || now - t >= FAIL_RETRY_MS;
@@ -2158,11 +2283,11 @@ async function ensureAppShortcuts(app, d, ps = defaultPs) {
2158
2283
  if (prev[k]) records[k] = prev[k];
2159
2284
  else delete records[k];
2160
2285
  }
2161
- const results = parseJsonArray(await ps(LNK_WRITE_PS, writeEnv(app, writes)));
2286
+ const results = parseJsonArray(await ps(LNK_WRITE_PS, writeEnv(app, writes, list)));
2162
2287
  let next = applyResults(app, d, writes, results, records, prev);
2163
2288
  if (!wanted()) {
2164
2289
  const list2 = parseList(await ps(LNK_LIST_PS));
2165
- if (list2) next = await restoreWith(app, d, list2, next, async (ops) => parseJsonArray(await ps(LNK_WRITE_PS, writeEnv(app, ops))));
2290
+ if (list2) next = await restoreWith(app, d, list2, next, async (ops) => parseJsonArray(await ps(LNK_WRITE_PS, writeEnv(app, ops, list2))));
2166
2291
  }
2167
2292
  saveRecords(next, fileOf(app, d));
2168
2293
  } finally {
@@ -2190,9 +2315,9 @@ function ensureAppShortcutsSync(app, d, ps = defaultPsSync) {
2190
2315
  return;
2191
2316
  }
2192
2317
  const prev = loadRecords(fileOf(app, d));
2193
- const { writes, records } = planPatchFor(app.exe, list, prev, portOf(app, d));
2318
+ const { writes, records } = planPatchFor(matcherOf(app, d), list, prev, portOf(app, d));
2194
2319
  if (writes.length === 0) return;
2195
- const results = parseJsonArray(ps(LNK_WRITE_PS, writeEnv(app, writes)));
2320
+ const results = parseJsonArray(ps(LNK_WRITE_PS, writeEnv(app, writes, list)));
2196
2321
  saveRecords(applyResults(app, d, writes, results, records, prev), fileOf(app, d));
2197
2322
  } finally {
2198
2323
  release2();
@@ -2213,7 +2338,7 @@ function restoreAppShortcutsSync(app, d, ps = defaultPsSync) {
2213
2338
  d.log(`\u8BFB\u4E0D\u4E86\u5FEB\u6377\u65B9\u5F0F\uFF0C${app.label}\u5FEB\u6377\u65B9\u5F0F\u6CA1\u8FD8\u539F\uFF08\u8BB0\u5F55\u4FDD\u7559\uFF0C\u4E0B\u6B21 disable \u518D\u8BD5\uFF09`);
2214
2339
  return;
2215
2340
  }
2216
- const keep = restoreWith(app, d, list, loadRecords(fileOf(app, d)), (ops) => parseJsonArray(ps(LNK_WRITE_PS, writeEnv(app, ops))));
2341
+ const keep = restoreWith(app, d, list, loadRecords(fileOf(app, d)), (ops) => parseJsonArray(ps(LNK_WRITE_PS, writeEnv(app, ops, list))));
2217
2342
  saveRecords(keep, fileOf(app, d));
2218
2343
  } finally {
2219
2344
  release2();
@@ -2221,14 +2346,14 @@ function restoreAppShortcutsSync(app, d, ps = defaultPsSync) {
2221
2346
  }
2222
2347
 
2223
2348
  // src/daemon/dumateApp/keeper.ts
2224
- import { readdirSync as readdirSync12, statSync as statSync10 } from "fs";
2225
- import { join as join24 } from "path";
2349
+ import { readdirSync as readdirSync13, statSync as statSync11 } from "fs";
2350
+ import { join as join25 } from "path";
2226
2351
 
2227
2352
  // src/daemon/dumateWatch.ts
2228
2353
  import { execFileSync as execFileSync10, spawn as spawn3 } from "child_process";
2229
- import { readdirSync as readdirSync10, realpathSync as realpathSync4, statSync as statSync9 } from "fs";
2354
+ import { readdirSync as readdirSync11, realpathSync as realpathSync4, statSync as statSync10 } from "fs";
2230
2355
  import { request as httpRequest } from "http";
2231
- import { join as join22 } from "path";
2356
+ import { join as join23 } from "path";
2232
2357
 
2233
2358
  // hook/ocTranslate.mjs
2234
2359
  function toCcTool(tool, args) {
@@ -2943,8 +3068,8 @@ function traeNoticeSummary(toolName, toolInput, described, notice) {
2943
3068
 
2944
3069
  // src/daemon/workbuddyApp/keeper.ts
2945
3070
  import { execFile as execFile6, spawn as spawn2 } from "child_process";
2946
- import { existsSync as existsSync18, mkdirSync as mkdirSync20, readdirSync as readdirSync9, readFileSync as readFileSync22, renameSync as renameSync14, writeFileSync as writeFileSync20 } from "fs";
2947
- import { delimiter as delimiter3, join as join21, resolve as resolve4 } from "path";
3071
+ import { existsSync as existsSync19, mkdirSync as mkdirSync21, readdirSync as readdirSync10, readFileSync as readFileSync24, renameSync as renameSync15, writeFileSync as writeFileSync21 } from "fs";
3072
+ import { delimiter as delimiter3, join as join22, resolve as resolve4 } from "path";
2948
3073
 
2949
3074
  // src/core/process.ts
2950
3075
  import { execFileSync as execFileSync5 } from "child_process";
@@ -3006,7 +3131,7 @@ function isAlive(pid, expectName, kill = process.kill.bind(process)) {
3006
3131
  }
3007
3132
 
3008
3133
  // src/core/state.ts
3009
- import { existsSync as existsSync13, mkdirSync as mkdirSync15, readdirSync as readdirSync5, readFileSync as readFileSync15, renameSync as renameSync10, rmSync as rmSync8, writeFileSync as writeFileSync15 } from "fs";
3134
+ import { existsSync as existsSync13, mkdirSync as mkdirSync15, readdirSync as readdirSync5, readFileSync as readFileSync16, renameSync as renameSync10, rmSync as rmSync8, writeFileSync as writeFileSync15 } from "fs";
3010
3135
  import { join as join14 } from "path";
3011
3136
 
3012
3137
  // src/core/codexServer.ts
@@ -3057,7 +3182,7 @@ function programOf(args) {
3057
3182
  }
3058
3183
 
3059
3184
  // src/core/ended.ts
3060
- import { closeSync, mkdirSync as mkdirSync12, openSync, readdirSync as readdirSync2, readFileSync as readFileSync13, readSync, realpathSync, renameSync as renameSync9, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync12 } from "fs";
3185
+ import { closeSync, mkdirSync as mkdirSync12, openSync, readdirSync as readdirSync2, readFileSync as readFileSync14, readSync, realpathSync, renameSync as renameSync9, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync12 } from "fs";
3061
3186
  import { join as join11 } from "path";
3062
3187
  var ENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
3063
3188
  var SID_RE = /^[A-Za-z0-9_-]{1,64}$/;
@@ -3079,7 +3204,7 @@ function writeEnded(s, now = Date.now()) {
3079
3204
  for (const n of readdirSync2(dir2)) {
3080
3205
  const f = join11(dir2, n);
3081
3206
  try {
3082
- if (now - statSync4(f).mtimeMs > ENDED_TTL_MS) rmSync5(f, { force: true });
3207
+ if (now - statSync5(f).mtimeMs > ENDED_TTL_MS) rmSync5(f, { force: true });
3083
3208
  } catch {
3084
3209
  }
3085
3210
  }
@@ -3089,7 +3214,7 @@ function writeEnded(s, now = Date.now()) {
3089
3214
  function readEnded(sessionId, now = Date.now()) {
3090
3215
  if (!SID_RE.test(sessionId)) return null;
3091
3216
  try {
3092
- const r = JSON.parse(readFileSync13(join11(endedDir(), `${sessionId}.json`), "utf8"));
3217
+ const r = JSON.parse(readFileSync14(join11(endedDir(), `${sessionId}.json`), "utf8"));
3093
3218
  if (!r || r.sessionId !== sessionId || typeof r.endedAt !== "number" || now - r.endedAt > ENDED_TTL_MS) return null;
3094
3219
  return r;
3095
3220
  } catch {
@@ -3104,7 +3229,7 @@ function projectDirName(cwd) {
3104
3229
  function readChunk(file3, fromEnd, max) {
3105
3230
  let fd;
3106
3231
  try {
3107
- const size = statSync4(file3).size;
3232
+ const size = statSync5(file3).size;
3108
3233
  const len = Math.min(size, max);
3109
3234
  const buf = Buffer.alloc(len);
3110
3235
  fd = openSync(file3, "r");
@@ -3137,7 +3262,7 @@ function claudeTranscriptCwd(sessionId, home = claudeHome()) {
3137
3262
  for (const d of dirs) {
3138
3263
  const file3 = join11(home, "projects", d, `${sessionId}.jsonl`);
3139
3264
  try {
3140
- statSync4(file3);
3265
+ statSync5(file3);
3141
3266
  } catch {
3142
3267
  continue;
3143
3268
  }
@@ -3173,14 +3298,14 @@ function resolveEndedCwd(sessionId, tool, d) {
3173
3298
  }
3174
3299
  function isDirSync(p) {
3175
3300
  try {
3176
- return statSync4(p).isDirectory();
3301
+ return statSync5(p).isDirectory();
3177
3302
  } catch {
3178
3303
  return false;
3179
3304
  }
3180
3305
  }
3181
3306
 
3182
3307
  // src/core/qoderAppSessions.ts
3183
- import { existsSync as existsSync11, mkdirSync as mkdirSync13, readdirSync as readdirSync3, readFileSync as readFileSync14, rmSync as rmSync6, writeFileSync as writeFileSync13 } from "fs";
3308
+ import { existsSync as existsSync11, mkdirSync as mkdirSync13, readdirSync as readdirSync3, readFileSync as readFileSync15, rmSync as rmSync6, writeFileSync as writeFileSync13 } from "fs";
3184
3309
  import { join as join12 } from "path";
3185
3310
  var QODER_APP_SESSION_TOOLS = ["qoder_cn", "qoder"];
3186
3311
  var KEEPER_FILE = "keeper";
@@ -3224,7 +3349,7 @@ function listQoderAppSessions(tool) {
3224
3349
  for (const f of names) {
3225
3350
  let t = "";
3226
3351
  try {
3227
- t = String(JSON.parse(readFileSync14(join12(dir(), f), "utf8")).tool ?? "");
3352
+ t = String(JSON.parse(readFileSync15(join12(dir(), f), "utf8")).tool ?? "");
3228
3353
  } catch {
3229
3354
  t = "";
3230
3355
  }
@@ -3238,7 +3363,7 @@ function qoderAppMarkedAt(sessionId) {
3238
3363
  const f = file(sessionId);
3239
3364
  if (!f) return 0;
3240
3365
  try {
3241
- const at = JSON.parse(readFileSync14(f, "utf8")).at;
3366
+ const at = JSON.parse(readFileSync15(f, "utf8")).at;
3242
3367
  return typeof at === "number" ? at : 0;
3243
3368
  } catch {
3244
3369
  return 0;
@@ -3311,7 +3436,7 @@ function statePath(sessionId) {
3311
3436
  }
3312
3437
  function readState(sessionId) {
3313
3438
  try {
3314
- return JSON.parse(readFileSync15(statePath(sessionId), "utf8"));
3439
+ return JSON.parse(readFileSync16(statePath(sessionId), "utf8"));
3315
3440
  } catch {
3316
3441
  return null;
3317
3442
  }
@@ -3376,7 +3501,7 @@ var STALE_STATES = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
3376
3501
  var STALE_NO_PID_LOG = "\u65E0\u8FDB\u7A0B\u53F7\u4E14 2 \u5C0F\u65F6\u65E0\u4E8B\u4EF6\uFF0C\u6309\u5DF2\u7ED3\u675F\u5904\u7406";
3377
3502
  function pendingApprovalSessions() {
3378
3503
  try {
3379
- const recs = JSON.parse(readFileSync15(join14(deskbuddyHome(), "approvals.json"), "utf8"));
3504
+ const recs = JSON.parse(readFileSync16(join14(deskbuddyHome(), "approvals.json"), "utf8"));
3380
3505
  return new Set(Array.isArray(recs) ? recs.filter((r) => r && r.status === "pending" && typeof r.sessionId === "string").map((r) => r.sessionId) : []);
3381
3506
  } catch {
3382
3507
  return /* @__PURE__ */ new Set();
@@ -3403,7 +3528,7 @@ function readAliveStates(tool, opts = {}) {
3403
3528
  if (!file3.endsWith(".json")) continue;
3404
3529
  let s;
3405
3530
  try {
3406
- s = JSON.parse(readFileSync15(join14(stateDir(), file3), "utf8"));
3531
+ s = JSON.parse(readFileSync16(join14(stateDir(), file3), "utf8"));
3407
3532
  } catch {
3408
3533
  rmSync8(join14(stateDir(), file3), { force: true });
3409
3534
  continue;
@@ -3455,7 +3580,7 @@ var ALIAS_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
3455
3580
  function resolveAlias(sessionId, now = Date.now()) {
3456
3581
  let map = {};
3457
3582
  try {
3458
- map = JSON.parse(readFileSync15(join14(deskbuddyHome(), "alias.json"), "utf8")) || {};
3583
+ map = JSON.parse(readFileSync16(join14(deskbuddyHome(), "alias.json"), "utf8")) || {};
3459
3584
  } catch {
3460
3585
  return sessionId;
3461
3586
  }
@@ -3470,7 +3595,7 @@ function resolveAlias(sessionId, now = Date.now()) {
3470
3595
 
3471
3596
  // src/wrap/shim.ts
3472
3597
  import { execFile as execFile3, execFileSync as execFileSync7 } from "child_process";
3473
- import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as mkdirSync16, readdirSync as readdirSync6, readFileSync as readFileSync16, rmdirSync, rmSync as rmSync9, writeFileSync as writeFileSync16 } from "fs";
3598
+ import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as mkdirSync16, readdirSync as readdirSync6, readFileSync as readFileSync17, rmdirSync, rmSync as rmSync9, writeFileSync as writeFileSync16 } from "fs";
3474
3599
  import { homedir as homedir3 } from "os";
3475
3600
  import { delimiter, join as join15, resolve as resolve3 } from "path";
3476
3601
  var SHIM_FALLBACK_EXIT = 213;
@@ -3626,7 +3751,7 @@ function removeRcBlock(content) {
3626
3751
  return content.slice(0, start) + content.slice(end);
3627
3752
  }
3628
3753
  function readRc(p) {
3629
- return readFileSync16(p).toString("latin1");
3754
+ return readFileSync17(p).toString("latin1");
3630
3755
  }
3631
3756
  function writeRc(p, latin1) {
3632
3757
  writeFileSync16(p, Buffer.from(latin1, "latin1"));
@@ -3881,7 +4006,7 @@ function ourShims(binDir) {
3881
4006
  }
3882
4007
  return names.filter((n) => {
3883
4008
  try {
3884
- return readFileSync16(join15(binDir, n), "utf8").includes(SHIM_MARKER);
4009
+ return readFileSync17(join15(binDir, n), "utf8").includes(SHIM_MARKER);
3885
4010
  } catch {
3886
4011
  return false;
3887
4012
  }
@@ -3893,7 +4018,7 @@ function uninstallShim(tool, e) {
3893
4018
  let removed = false;
3894
4019
  const file3 = shimFile(e.binDir, command, e.platform);
3895
4020
  try {
3896
- if (existsSync14(file3) && readFileSync16(file3, "utf8").includes(SHIM_MARKER)) {
4021
+ if (existsSync14(file3) && readFileSync17(file3, "utf8").includes(SHIM_MARKER)) {
3897
4022
  rmSync9(file3, { force: true });
3898
4023
  removed = true;
3899
4024
  }
@@ -3937,7 +4062,7 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
3937
4062
  const file3 = shimFile(binDir, command, platform);
3938
4063
  let installed = false;
3939
4064
  try {
3940
- installed = readFileSync16(file3, "utf8").includes(SHIM_MARKER);
4065
+ installed = readFileSync17(file3, "utf8").includes(SHIM_MARKER);
3941
4066
  } catch {
3942
4067
  installed = false;
3943
4068
  }
@@ -3949,18 +4074,18 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
3949
4074
 
3950
4075
  // src/daemon/workbuddyAppDb.ts
3951
4076
  import { execFile as execFile5 } from "child_process";
3952
- import { closeSync as closeSync5, existsSync as existsSync17, mkdirSync as mkdirSync19, openSync as openSync5, readdirSync as readdirSync8, readFileSync as readFileSync21, readSync as readSync5, renameSync as renameSync13, rmSync as rmSync12, statSync as statSync8, writeFileSync as writeFileSync19 } from "fs";
4077
+ import { closeSync as closeSync5, existsSync as existsSync18, mkdirSync as mkdirSync20, openSync as openSync5, readdirSync as readdirSync9, readFileSync as readFileSync23, readSync as readSync5, renameSync as renameSync14, rmSync as rmSync12, statSync as statSync9, writeFileSync as writeFileSync20 } from "fs";
3953
4078
  import { createRequire as createRequire3 } from "module";
3954
- import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute2, join as join20 } from "path";
4079
+ import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute2, join as join21 } from "path";
3955
4080
 
3956
4081
  // src/core/launch.ts
3957
- import { mkdirSync as mkdirSync18, readFileSync as readFileSync18, renameSync as renameSync12, rmSync as rmSync11, writeFileSync as writeFileSync18 } from "fs";
4082
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync19, renameSync as renameSync12, rmSync as rmSync11, writeFileSync as writeFileSync18 } from "fs";
3958
4083
  import { join as join17 } from "path";
3959
4084
 
3960
4085
  // src/core/wake.ts
3961
4086
  import { execFileSync as execFileSync8 } from "child_process";
3962
4087
  import { createHash as createHash2, createHmac, timingSafeEqual } from "crypto";
3963
- import { chmodSync as chmodSync4, closeSync as closeSync2, mkdirSync as mkdirSync17, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync17, readSync as readSync2, renameSync as renameSync11, rmSync as rmSync10, statSync as statSync5, writeFileSync as writeFileSync17 } from "fs";
4088
+ import { chmodSync as chmodSync4, closeSync as closeSync2, mkdirSync as mkdirSync17, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync18, readSync as readSync2, renameSync as renameSync11, rmSync as rmSync10, statSync as statSync6, writeFileSync as writeFileSync17 } from "fs";
3964
4089
  import { tmpdir } from "os";
3965
4090
  import { join as join16 } from "path";
3966
4091
  function wakeDir(home = deskbuddyHome()) {
@@ -4029,7 +4154,7 @@ function isWakeMarked(sessionId, alive = pidExists) {
4029
4154
  const safe = safeSid(sessionId);
4030
4155
  if (!safe) return false;
4031
4156
  try {
4032
- const m = JSON.parse(readFileSync17(join16(marksDir(), `${safe}.json`), "utf8"));
4157
+ const m = JSON.parse(readFileSync18(join16(marksDir(), `${safe}.json`), "utf8"));
4033
4158
  const daemonPid = Number(m?.daemonPid) || 0;
4034
4159
  if (!daemonPid || !alive(daemonPid)) return false;
4035
4160
  const pid = Number(m?.agentPid) || 0;
@@ -4099,16 +4224,17 @@ function macEqual(a, b) {
4099
4224
  var WAKE_MARK_RE = /\[DeskBuddy#([0-9A-Za-z_-]{1,64})(?::[0-9a-f]{32})?\]/;
4100
4225
  var INTERRUPT_RE = /^\[Request interrupted by user/;
4101
4226
  var TRANSCRIPT_TAIL2 = 256 * 1024;
4227
+ var TEXT_PART_TYPES = /* @__PURE__ */ new Set(["text", "input_text", "output_text"]);
4102
4228
  function userText(content) {
4103
4229
  if (typeof content === "string") return content;
4104
4230
  if (!Array.isArray(content)) return "";
4105
- return content.filter((x) => x && x.type === "text").map((x) => String(x.text ?? "")).join("\n");
4231
+ return content.filter((x) => x && TEXT_PART_TYPES.has(x.type)).map((x) => String(x.text ?? "")).join("\n");
4106
4232
  }
4107
4233
  function analyzeTranscript(file3) {
4108
4234
  if (!file3) return null;
4109
4235
  let fd;
4110
4236
  try {
4111
- const size = statSync5(file3).size;
4237
+ const size = statSync6(file3).size;
4112
4238
  const len = Math.min(size, TRANSCRIPT_TAIL2);
4113
4239
  const buf = Buffer.alloc(len);
4114
4240
  fd = openSync2(file3, "r");
@@ -4127,7 +4253,9 @@ function analyzeTranscript(file3) {
4127
4253
  } catch {
4128
4254
  continue;
4129
4255
  }
4130
- if (!o || !o.message) continue;
4256
+ if (!o) continue;
4257
+ if (o.type === "message" && (o.role === "user" || o.role === "assistant")) o = { type: o.role, message: { content: o.content } };
4258
+ if (!o.message) continue;
4131
4259
  if (o.type === "assistant") {
4132
4260
  const t = userText(o.message.content).trim();
4133
4261
  if (t) {
@@ -4193,7 +4321,7 @@ function claimLaunch(runId) {
4193
4321
  return null;
4194
4322
  }
4195
4323
  try {
4196
- const p = JSON.parse(readFileSync18(claimed, "utf8"));
4324
+ const p = JSON.parse(readFileSync19(claimed, "utf8"));
4197
4325
  if (!p || typeof p.cwd !== "string" || typeof p.resume !== "string") return null;
4198
4326
  if (p.tool === void 0) p.tool = "claude_code";
4199
4327
  return validPlan(p) ? p : null;
@@ -4208,7 +4336,7 @@ function claimLaunch(runId) {
4208
4336
  }
4209
4337
  function launchPending(runId) {
4210
4338
  try {
4211
- readFileSync18(planFile(runId));
4339
+ readFileSync19(planFile(runId));
4212
4340
  return true;
4213
4341
  } catch {
4214
4342
  return false;
@@ -4216,16 +4344,16 @@ function launchPending(runId) {
4216
4344
  }
4217
4345
 
4218
4346
  // src/core/title.ts
4219
- import { closeSync as closeSync3, existsSync as existsSync15, openSync as openSync3, readFileSync as readFileSync19, readSync as readSync3, statSync as statSync6 } from "fs";
4347
+ import { closeSync as closeSync3, existsSync as existsSync15, openSync as openSync3, readFileSync as readFileSync20, readSync as readSync3, statSync as statSync7 } from "fs";
4220
4348
  import { createRequire as createRequire2 } from "module";
4221
4349
  import { homedir as homedir5 } from "os";
4222
4350
  import { basename as basename3, dirname as dirname10, isAbsolute, join as join18 } from "path";
4223
4351
 
4224
4352
  // src/core/dirFilter.ts
4225
4353
  import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
4226
- import { posix, win32 as win322 } from "path";
4354
+ import { posix, win32 as win323 } from "path";
4227
4355
  function norm(p, win) {
4228
- if (win) return win322.resolve(p).replace(/[\\/]+$/, "").toLowerCase();
4356
+ if (win) return win323.resolve(p).replace(/[\\/]+$/, "").toLowerCase();
4229
4357
  return posix.resolve(p).replace(/\/+$/, "");
4230
4358
  }
4231
4359
  function isUnder(p, base, win) {
@@ -4234,7 +4362,7 @@ function isUnder(p, base, win) {
4234
4362
  function isAutoWorkspace(p, opts = {}) {
4235
4363
  if (!p) return false;
4236
4364
  const win = (opts.platform ?? process.platform) === "win32";
4237
- const P = win ? win322 : posix;
4365
+ const P = win ? win323 : posix;
4238
4366
  const home = norm(opts.home ?? homedir4(), win);
4239
4367
  const full = norm(p, win);
4240
4368
  const name = P.basename(full);
@@ -4245,7 +4373,7 @@ function isAutoWorkspace(p, opts = {}) {
4245
4373
  function isReportableDir(p, opts = {}) {
4246
4374
  if (!p) return false;
4247
4375
  const win = (opts.platform ?? process.platform) === "win32";
4248
- const P = win ? win322 : posix;
4376
+ const P = win ? win323 : posix;
4249
4377
  const full = norm(p, win);
4250
4378
  if (full === norm(P.parse(P.resolve(p)).root, win)) return false;
4251
4379
  if (full === norm(opts.home ?? homedir4(), win)) return false;
@@ -4278,9 +4406,9 @@ function qoderDbPaths(tool, platform = process.platform, home = homedir5(), env
4278
4406
 
4279
4407
  // src/daemon/locate.ts
4280
4408
  import { execFile as execFile4, execFileSync as execFileSync9 } from "child_process";
4281
- import { accessSync, closeSync as closeSync4, constants, existsSync as existsSync16, lstatSync, openSync as openSync4, readFileSync as readFileSync20, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
4409
+ import { accessSync, closeSync as closeSync4, constants, existsSync as existsSync17, lstatSync, openSync as openSync4, readFileSync as readFileSync22, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
4282
4410
  import { homedir as homedir6 } from "os";
4283
- import { delimiter as delimiter2, dirname as dirname11, join as join19, win32 as win323 } from "path";
4411
+ import { delimiter as delimiter2, dirname as dirname11, join as join20, win32 as win325 } from "path";
4284
4412
 
4285
4413
  // src/core/agentEnv.ts
4286
4414
  var BUN_HIJACK_ENV = ["BUN_BE_BUN", "BUN_OPTIONS"];
@@ -4297,6 +4425,280 @@ function scrubForCommand(cmd, env = process.env) {
4297
4425
  return true;
4298
4426
  }
4299
4427
 
4428
+ // src/daemon/winAppLocator.ts
4429
+ import { existsSync as existsSync16, mkdirSync as mkdirSync19, readdirSync as readdirSync8, readFileSync as readFileSync21, renameSync as renameSync13, writeFileSync as writeFileSync19 } from "fs";
4430
+ import { join as join19, win32 as win324 } from "path";
4431
+ var SOURCE_TEXT = {
4432
+ process: "\u6B63\u5728\u8FD0\u884C\u7684\u8FDB\u7A0B",
4433
+ remembered: "\u4E4B\u524D\u8BB0\u4E0B\u7684\u4F4D\u7F6E",
4434
+ uninstall: "\u6CE8\u518C\u8868\u5378\u8F7D\u9879",
4435
+ shortcut: "\u5FEB\u6377\u65B9\u5F0F",
4436
+ appPaths: "App Paths",
4437
+ protocol: "URL \u534F\u8BAE",
4438
+ default: "\u9ED8\u8BA4\u76EE\u5F55",
4439
+ path: "PATH"
4440
+ };
4441
+ var QODER_CN_WORKER_REL = "resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-cn-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
4442
+ var QODER_WORKER_REL = "resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
4443
+ function stdDirs(env, ...names) {
4444
+ const out = [];
4445
+ for (const n of names) {
4446
+ if (env.localAppData) out.push(win324.join(env.localAppData, "Programs", n));
4447
+ out.push(win324.join(env.programFiles || "C:\\Program Files", n));
4448
+ out.push(win324.join(env.programFilesX86 || "C:\\Program Files (x86)", n));
4449
+ }
4450
+ return out;
4451
+ }
4452
+ var WIN_APP_SPECS = {
4453
+ qoder_cn: { id: "qoder_cn", label: "Qoder CN", exe: "Qoder CN.exe", names: /^Qoder CN(\s|$)/i, protocols: ["qoder-cn"], required: [QODER_CN_WORKER_REL], defaultDirs: (e) => stdDirs(e, "Qoder CN") },
4454
+ qoder: { id: "qoder", label: "Qoder", exe: "Qoder.exe", names: /^Qoder(?! CN)(\s|$)/i, protocols: ["qoder"], required: [QODER_WORKER_REL], defaultDirs: (e) => stdDirs(e, "Qoder") },
4455
+ // 与原 locateWorkbuddyAppExe 一致:DisplayName 含 WorkBuddy、只要 WorkBuddy.exe 在
4456
+ workbuddy: { id: "workbuddy", label: "WorkBuddy", exe: "WorkBuddy.exe", names: /WorkBuddy/i, protocols: ["workbuddy"], required: [], defaultDirs: (e) => stdDirs(e, "WorkBuddy") },
4457
+ qwenwork: { id: "qwenwork", label: "\u5343\u95EE\u529E\u516C", exe: "QwenWorkCN.exe", names: /千问办公|QwenWork/i, protocols: ["qwenwork-cn"], required: [], defaultDirs: (e) => stdDirs(e, "QwenWorkCN") },
4458
+ dumate: { id: "dumate", label: "\u767E\u5EA6\u642D\u5B50", exe: "DuMate.exe", names: /^DuMate(\s|$)/i, protocols: ["dumate"], required: [], defaultDirs: (e) => stdDirs(e, "DuMate", "qianfan-desktop-app") }
4459
+ };
4460
+ var defaultLocFs = {
4461
+ exists: (p) => existsSync16(p),
4462
+ readText: (p) => readSmallText(p),
4463
+ subdirs: (p) => {
4464
+ try {
4465
+ return readdirSync8(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
4466
+ } catch {
4467
+ return [];
4468
+ }
4469
+ }
4470
+ };
4471
+ function validExe(p, spec, fs) {
4472
+ if (!isValidWinPath(p) || win324.basename(p).toLowerCase() !== spec.exe.toLowerCase() || !fs.exists(p)) return false;
4473
+ const dir2 = win324.dirname(p);
4474
+ return spec.required.every((r) => fs.exists(win324.join(dir2, ...r.split("/"))));
4475
+ }
4476
+ function expandHint(hint, spec, fs, deep = true) {
4477
+ const out = [];
4478
+ const add = (p) => {
4479
+ if (p && isValidWinPath(p) && !out.some((x) => x.toLowerCase() === p.toLowerCase())) out.push(p);
4480
+ };
4481
+ const base = win324.basename(hint);
4482
+ if (base.toLowerCase() === spec.exe.toLowerCase()) add(hint);
4483
+ const dir2 = /\.[a-z0-9]{2,4}$/i.test(base) ? win324.dirname(hint) : hint;
4484
+ add(win324.join(dir2, spec.exe));
4485
+ const ini = fs.readText(win324.join(dir2, "install.ini"));
4486
+ if (ini) add(launcherTarget(ini, spec.exe));
4487
+ if (deep) for (const sub of fs.subdirs(dir2).filter((s) => /^[0-9a-z][0-9a-z._-]*$/i.test(s)).sort().reverse().slice(0, 20)) add(win324.join(dir2, sub, spec.exe));
4488
+ return out;
4489
+ }
4490
+ var WIN_LOCATE_PS = String.raw`[Console]::OutputEncoding=[Text.Encoding]::UTF8
4491
+ # PowerShell 5.1 的 ConvertFrom-Json 把数组当一个对象输出,经 ForEach-Object 展开成元素(SF-Test 实测,否则 -contains 全不中)
4492
+ $exes = @($env:DB_LOC_EXES | ConvertFrom-Json | ForEach-Object { $_ } | ForEach-Object { [string]$_ }); $protos = @($env:DB_LOC_PROTOS | ConvertFrom-Json | ForEach-Object { $_ } | ForEach-Object { [string]$_ })
4493
+ $o = [ordered]@{ procs = @(); uninstall = @(); appPaths = @(); protocols = @() }
4494
+ try { $o.procs = @(Get-CimInstance Win32_Process -ErrorAction Stop | Where-Object { $exes -contains $_.Name } | ForEach-Object { [pscustomobject]@{ name = [string]$_.Name; path = [string]$_.ExecutablePath } }) } catch {}
4495
+ $o.uninstall = @(Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*','HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*','HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName } | ForEach-Object { [pscustomobject]@{ name = [string]$_.DisplayName; il = [string]$_.InstallLocation; icon = [string]$_.DisplayIcon; un = [string]$_.UninstallString } })
4496
+ foreach ($e in $exes) { foreach ($h in @('HKCU:','HKLM:')) { $p = Get-ItemProperty -LiteralPath "$h\Software\Microsoft\Windows\CurrentVersion\App Paths\$e" -ErrorAction SilentlyContinue; if ($p) { $o.appPaths += [pscustomobject]@{ exe = $e; def = [string]$p.'(default)'; path = [string]$p.Path } } } }
4497
+ foreach ($n in $protos) { foreach ($h in @('HKCU:\Software\Classes','HKLM:\Software\Classes')) { $c = (Get-ItemProperty -LiteralPath "$h\$n\shell\open\command" -ErrorAction SilentlyContinue).'(default)'; if ($c) { $o.protocols += [pscustomobject]@{ name = $n; cmd = [string]$c } } } }
4498
+ ConvertTo-Json -InputObject $o -Compress -Depth 4`;
4499
+ var str = (v) => typeof v === "string" ? v : "";
4500
+ var arr = (v) => (Array.isArray(v) ? v : v && typeof v === "object" ? [v] : []).filter((x) => x && typeof x === "object");
4501
+ function parseFacts(out) {
4502
+ if (out === null) return null;
4503
+ try {
4504
+ const v = JSON.parse(out.trim());
4505
+ if (!v || typeof v !== "object") return null;
4506
+ return {
4507
+ procs: arr(v.procs).map((x) => ({ name: str(x.name), path: str(x.path) })),
4508
+ uninstall: arr(v.uninstall).map((x) => ({ name: str(x.name), il: str(x.il), icon: str(x.icon), un: str(x.un) })),
4509
+ appPaths: arr(v.appPaths).map((x) => ({ exe: str(x.exe), def: str(x.def), path: str(x.path) })),
4510
+ protocols: arr(v.protocols).map((x) => ({ name: str(x.name), cmd: str(x.cmd) }))
4511
+ };
4512
+ } catch {
4513
+ return null;
4514
+ }
4515
+ }
4516
+ function parseLnks(out) {
4517
+ if (out === null) return null;
4518
+ try {
4519
+ const v = JSON.parse(out.trim() || "[]");
4520
+ return arr(v).map((x) => ({ path: str(x.path), target: str(x.target) })).filter((x) => x.path && x.target);
4521
+ } catch {
4522
+ return null;
4523
+ }
4524
+ }
4525
+ function locateFromFacts(spec, facts, lnks, remembered, env, fs) {
4526
+ const exeLow = spec.exe.toLowerCase();
4527
+ const groups = [
4528
+ { source: "process", hints: (facts?.procs ?? []).filter((p) => p.name.toLowerCase() === exeLow).map((p) => p.path), failed: facts === null },
4529
+ { source: "remembered", hints: remembered ? [remembered] : [] },
4530
+ { source: "uninstall", hints: (facts?.uninstall ?? []).filter((u) => spec.names.test(u.name.trim())).flatMap((u) => [u.il, u.icon, u.un]), failed: facts === null },
4531
+ {
4532
+ source: "shortcut",
4533
+ hints: (lnks ?? []).filter((l) => win324.basename(l.target).toLowerCase() === exeLow || spec.names.test(win324.basename(l.path).replace(/\.lnk$/i, ""))).map((l) => l.target),
4534
+ failed: lnks === null
4535
+ },
4536
+ { source: "appPaths", hints: (facts?.appPaths ?? []).filter((a) => a.exe.toLowerCase() === exeLow).flatMap((a) => [a.def, a.path]), failed: facts === null },
4537
+ { source: "protocol", hints: (facts?.protocols ?? []).filter((p) => spec.protocols.includes(p.name.toLowerCase())).map((p) => p.cmd), failed: facts === null },
4538
+ { source: "default", hints: spec.defaultDirs(env) },
4539
+ { source: "path", hints: (env.path ?? "").split(";").filter((x) => x.trim()).map((x) => x.trim()) }
4540
+ ];
4541
+ const notes = [];
4542
+ for (const g of groups) {
4543
+ const hints = g.hints.map(normalizeHint).filter((h) => h !== null);
4544
+ for (const h of hints) for (const c of expandHint(h, spec, fs, g.source !== "path")) if (validExe(c, spec, fs)) return { found: { exe: c, source: g.source }, tried: "" };
4545
+ if (g.source === "remembered" && hints.length === 0) continue;
4546
+ const what = g.failed ? "\u67E5\u8BE2\u5931\u8D25\u6216\u8D85\u65F6" : hints.length === 0 ? "\u6CA1\u6709\u7EBF\u7D22" : `${hints.length} \u6761\u7EBF\u7D22\u90FD\u4E0D\u5408\u683C\uFF08\u5982 ${hints[0]}\uFF09`;
4547
+ notes.push(`${SOURCE_TEXT[g.source]}\uFF1A${what}`);
4548
+ }
4549
+ return { found: null, tried: notes.join("\uFF1B") };
4550
+ }
4551
+ var WIN_APPS_FILE = "winapps.json";
4552
+ var LOCATE_FAIL_LOG_MS = 6 * 60 * 60 * 1e3;
4553
+ var LOCATE_MIN_GAP_MS = 60 * 1e3;
4554
+ var LOCATE_EVERY_MS = 10 * 60 * 1e3;
4555
+ var cacheFile = () => join19(deskbuddyHome(), WIN_APPS_FILE);
4556
+ function loadCache(file3 = cacheFile()) {
4557
+ try {
4558
+ const v = JSON.parse(readFileSync21(file3, "utf8"));
4559
+ const out = {};
4560
+ for (const [k, e] of Object.entries(v ?? {})) {
4561
+ const x = e;
4562
+ if (x && isValidWinPath(x.exe) && typeof x.source === "string" && x.source in SOURCE_TEXT) out[k] = { exe: x.exe, source: x.source, at: typeof x.at === "number" ? x.at : 0 };
4563
+ }
4564
+ return out;
4565
+ } catch {
4566
+ return {};
4567
+ }
4568
+ }
4569
+ function saveCache(c, file3) {
4570
+ try {
4571
+ mkdirSync19(join19(file3, ".."), { recursive: true });
4572
+ writeFileSync19(file3 + ".tmp", JSON.stringify(c, null, 2));
4573
+ renameSync13(file3 + ".tmp", file3);
4574
+ } catch {
4575
+ }
4576
+ }
4577
+ function cachedWinAppExe(id, fs = defaultLocFs, file3 = cacheFile()) {
4578
+ const spec = WIN_APP_SPECS[id];
4579
+ const e = spec ? loadCache(file3)[id] : void 0;
4580
+ return spec && e && validExe(e.exe, spec, fs) ? e.exe : null;
4581
+ }
4582
+ var WinAppLocator = class {
4583
+ #d;
4584
+ /** 正在进行的查询(只覆盖 ids 里的工具) */
4585
+ #inflight = null;
4586
+ /** 每个工具上次查完的时间(最小间隔按工具分别记) */
4587
+ #lastRunAt = /* @__PURE__ */ new Map();
4588
+ #logFn;
4589
+ #lastResult = {};
4590
+ #failLogged = /* @__PURE__ */ new Map();
4591
+ constructor(d = {}) {
4592
+ this.#d = d;
4593
+ this.#logFn = d.log;
4594
+ }
4595
+ /** 补上日志函数(先被不带日志的调用方建出来时,daemon 后给的 log 也要生效);已有的不覆盖 */
4596
+ setLog(log) {
4597
+ if (log && !this.#logFn) this.#logFn = log;
4598
+ }
4599
+ get hasLog() {
4600
+ return !!this.#logFn;
4601
+ }
4602
+ #now() {
4603
+ return this.#d.now ? this.#d.now() : Date.now();
4604
+ }
4605
+ #active() {
4606
+ if ((this.#d.platform ?? process.platform) !== "win32") return false;
4607
+ return !(this.#d.skip ? this.#d.skip() : process.env.DESKBUDDY_SKIP_WIN_LOCATE === "1");
4608
+ }
4609
+ #env() {
4610
+ return this.#d.env ?? { localAppData: process.env.LOCALAPPDATA, programFiles: process.env.ProgramFiles, programFilesX86: process.env["ProgramFiles(x86)"], path: process.env.PATH };
4611
+ }
4612
+ /**
4613
+ * 查一遍(一次 PowerShell 查进程 / 注册表 + 一次枚举快捷方式,异步带超时),结果写缓存。
4614
+ * 正在进行的查询已覆盖这些工具 → 共用它;没覆盖 → 等它结束再查自己这批(不并发起两次 PowerShell)
4615
+ */
4616
+ refresh(ids = Object.keys(WIN_APP_SPECS)) {
4617
+ if (!this.#active()) return Promise.resolve({});
4618
+ const want = ids.filter((i) => WIN_APP_SPECS[i]);
4619
+ const cur = this.#inflight;
4620
+ if (cur && want.every((i) => cur.ids.has(i))) return cur.p;
4621
+ const p = (cur ? cur.p.then(() => void 0, () => void 0) : Promise.resolve()).then(() => this.#run(want));
4622
+ const entry = { ids: new Set(want), p };
4623
+ this.#inflight = entry;
4624
+ void p.finally(() => {
4625
+ if (this.#inflight === entry) this.#inflight = null;
4626
+ }).catch(() => void 0);
4627
+ return p;
4628
+ }
4629
+ /** 某一个工具的主程序:只查这一个工具;它近 60 秒内查过就直接用缓存(正在查它就等查完)。非 Windows / 测试兜底返回缓存 */
4630
+ async locate(id) {
4631
+ const fs = this.#d.fs ?? defaultLocFs;
4632
+ const file3 = this.#d.file ?? cacheFile();
4633
+ if (this.#active() && WIN_APP_SPECS[id]) {
4634
+ const cur = this.#inflight;
4635
+ if (cur && cur.ids.has(id)) await cur.p.catch(() => void 0);
4636
+ else if (this.#now() - (this.#lastRunAt.get(id) ?? -Infinity) >= LOCATE_MIN_GAP_MS) await this.refresh([id]).catch(() => void 0);
4637
+ }
4638
+ return cachedWinAppExe(id, fs, file3);
4639
+ }
4640
+ async #run(ids) {
4641
+ const ps = this.#d.ps ?? defaultPs;
4642
+ const fs = this.#d.fs ?? defaultLocFs;
4643
+ const file3 = this.#d.file ?? cacheFile();
4644
+ const specs = ids.map((i) => WIN_APP_SPECS[i]);
4645
+ const facts = parseFacts(
4646
+ await ps(WIN_LOCATE_PS, { DB_LOC_EXES: JSON.stringify(specs.map((s) => s.exe)), DB_LOC_PROTOS: JSON.stringify(specs.flatMap((s) => s.protocols)) }).catch(() => null)
4647
+ );
4648
+ const lnks = parseLnks(await ps(LNK_LIST_PS).catch(() => null));
4649
+ const cache2 = loadCache(file3);
4650
+ const out = {};
4651
+ let dirty = false;
4652
+ for (const spec of specs) {
4653
+ const prev = cache2[spec.id];
4654
+ const r = locateFromFacts(spec, facts, lnks, prev?.exe ?? null, this.#env(), fs);
4655
+ out[spec.id] = r.found;
4656
+ if (r.found) {
4657
+ this.#failLogged.delete(spec.id);
4658
+ const changed = !prev || prev.exe.toLowerCase() !== r.found.exe.toLowerCase();
4659
+ if (changed) this.#log(`${spec.label} \u684C\u9762\u7248\u4F4D\u7F6E\uFF1A${r.found.exe}\uFF08\u4ECE${SOURCE_TEXT[r.found.source]}\u627E\u5230\uFF09`);
4660
+ if (changed || r.found.source !== "remembered") {
4661
+ cache2[spec.id] = { exe: r.found.exe, source: r.found.source, at: this.#now() };
4662
+ dirty = true;
4663
+ }
4664
+ } else {
4665
+ if (prev) {
4666
+ delete cache2[spec.id];
4667
+ dirty = true;
4668
+ }
4669
+ this.#noteMissing(spec, r.tried);
4670
+ }
4671
+ }
4672
+ if (dirty) saveCache(cache2, file3);
4673
+ for (const id of ids) this.#lastRunAt.set(id, this.#now());
4674
+ this.#lastResult = out;
4675
+ return out;
4676
+ }
4677
+ /** 同一原因 LOCATE_FAIL_LOG_MS 内只记一次(不刷屏) */
4678
+ #noteMissing(spec, tried) {
4679
+ const key = `${spec.id}\0${tried}`;
4680
+ const at = this.#failLogged.get(key);
4681
+ const now = this.#now();
4682
+ if (at !== void 0 && now - at < LOCATE_FAIL_LOG_MS) return;
4683
+ for (const k of this.#failLogged.keys()) if (k.startsWith(`${spec.id}\0`)) this.#failLogged.delete(k);
4684
+ this.#failLogged.set(key, now);
4685
+ this.#log(`\u672C\u673A\u627E\u4E0D\u5230 ${spec.label} \u684C\u9762\u7248\uFF08${tried}\uFF09`);
4686
+ }
4687
+ #log(msg) {
4688
+ this.#logFn?.(msg);
4689
+ }
4690
+ /** 测试用:上一轮结果 */
4691
+ get lastResult() {
4692
+ return this.#lastResult;
4693
+ }
4694
+ };
4695
+ var shared = null;
4696
+ function winAppLocator(log) {
4697
+ if (!shared) shared = new WinAppLocator({ log });
4698
+ else shared.setLog(log);
4699
+ return shared;
4700
+ }
4701
+
4300
4702
  // src/daemon/locate.ts
4301
4703
  function qoderWorker(d, worker, appBin) {
4302
4704
  if (d.isExecutable(appBin)) return { file: appBin, prefixArgs: [worker], env: { ELECTRON_RUN_AS_NODE: "1" } };
@@ -4308,14 +4710,14 @@ var defaultLocateDeps = () => ({
4308
4710
  nodePath: process.execPath,
4309
4711
  isExecutable: (p) => {
4310
4712
  try {
4311
- if (!statSync7(p).isFile()) return false;
4713
+ if (!statSync8(p).isFile()) return false;
4312
4714
  accessSync(p, constants.X_OK);
4313
4715
  return true;
4314
4716
  } catch {
4315
4717
  return false;
4316
4718
  }
4317
4719
  },
4318
- exists: (p) => existsSync16(p),
4720
+ exists: (p) => existsSync17(p),
4319
4721
  lexists: (p) => {
4320
4722
  try {
4321
4723
  lstatSync(p);
@@ -4329,15 +4731,16 @@ var defaultLocateDeps = () => ({
4329
4731
  localAppData: process.env.LOCALAPPDATA ?? "",
4330
4732
  readText: (p) => {
4331
4733
  try {
4332
- return readFileSync20(p, "utf8");
4734
+ return readFileSync22(p, "utf8");
4333
4735
  } catch {
4334
4736
  return null;
4335
4737
  }
4336
4738
  },
4337
4739
  // 🔴 daemon 自己也不能找到垫片:远程任务要的是真程序(窗口接管 spec 2026-09-30 §3.1)
4338
- skipDirs: [join19(deskbuddyHome(), "bin")],
4740
+ skipDirs: [join20(deskbuddyHome(), "bin")],
4339
4741
  winAppHints: (product, exe) => winInstallDirs(product, exe),
4340
4742
  winAppExeHints: () => winWorkbuddyExeHints(),
4743
+ winAppExe: (id) => cachedWinAppExe(id),
4341
4744
  isPe: (p) => {
4342
4745
  let fd = null;
4343
4746
  try {
@@ -4412,7 +4815,7 @@ function classifyAsyncError(e, stdout2, stderr) {
4412
4815
  var defaultProbe = createVersionProbe({
4413
4816
  stat: (p) => {
4414
4817
  try {
4415
- const st = statSync7(p);
4818
+ const st = statSync8(p);
4416
4819
  return { size: st.size, mtimeMs: st.mtimeMs };
4417
4820
  } catch {
4418
4821
  return null;
@@ -4460,9 +4863,9 @@ function installDirsFromProcs(procs, exe) {
4460
4863
  const out = [];
4461
4864
  for (const p of procs ?? []) {
4462
4865
  const first = winSplitArgs(p.cmdline)[0] ?? "";
4463
- if (win323.basename(first).toLowerCase() !== exe.toLowerCase()) continue;
4464
- const dir2 = win323.dirname(first);
4465
- if (win323.isAbsolute(dir2) && !out.some((d) => d.toLowerCase() === dir2.toLowerCase())) out.push(dir2);
4866
+ if (win325.basename(first).toLowerCase() !== exe.toLowerCase()) continue;
4867
+ const dir2 = win325.dirname(first);
4868
+ if (win325.isAbsolute(dir2) && !out.some((d) => d.toLowerCase() === dir2.toLowerCase())) out.push(dir2);
4466
4869
  }
4467
4870
  return out;
4468
4871
  }
@@ -4470,8 +4873,8 @@ function parseUninstallDirs(out) {
4470
4873
  const dirs = [];
4471
4874
  for (const raw of out.split(/\r?\n/)) {
4472
4875
  let v = raw.trim().replace(/,\s*-?\d+$/, "").replace(/^"|"$/g, "").trim();
4473
- if (!v || !win323.isAbsolute(v)) continue;
4474
- if (/\.exe$/i.test(v)) v = win323.dirname(v);
4876
+ if (!v || !win325.isAbsolute(v)) continue;
4877
+ if (/\.exe$/i.test(v)) v = win325.dirname(v);
4475
4878
  v = v.replace(/[\\/]+$/, "");
4476
4879
  if (!dirs.some((d) => d.toLowerCase() === v.toLowerCase())) dirs.push(v);
4477
4880
  }
@@ -4498,7 +4901,7 @@ function parseExeHints(out) {
4498
4901
  const res = [];
4499
4902
  for (const raw of out.split(/\r?\n/)) {
4500
4903
  const v = raw.trim().replace(/,\s*-?\d+$/, "").replace(/^"|"$/g, "").trim().replace(/[\\/]+$/, "");
4501
- if (!v || !win323.isAbsolute(v)) continue;
4904
+ if (!v || !win325.isAbsolute(v)) continue;
4502
4905
  if (!res.some((x) => x.toLowerCase() === v.toLowerCase())) res.push(v);
4503
4906
  }
4504
4907
  return res;
@@ -4522,57 +4925,54 @@ function winWorkbuddyExeHints() {
4522
4925
  function locateWorkbuddyAppExe(d = defaultLocateDeps()) {
4523
4926
  if (!isWin(d)) {
4524
4927
  for (const dir2 of appDirs(d.home)) {
4525
- const p = join19(dir2, "WorkBuddy.app");
4928
+ const p = join20(dir2, "WorkBuddy.app");
4526
4929
  if (d.exists(p)) return p;
4527
4930
  }
4528
4931
  return null;
4529
4932
  }
4530
- return pickWorkbuddyExe(winAppDirs(d, "WorkBuddy"), d) ?? pickWorkbuddyExe(d.winAppExeHints?.() ?? [], d);
4933
+ return pickWorkbuddyExe(locatedExe(d, "workbuddy"), d) ?? pickWorkbuddyExe(winAppDirs(d, "WorkBuddy"), d) ?? pickWorkbuddyExe(d.winAppExeHints?.() ?? [], d);
4934
+ }
4935
+ function locatedExe(d, id) {
4936
+ const p = d.winAppExe?.(id);
4937
+ return p ? [p] : [];
4938
+ }
4939
+ function locatedDirs(d, id) {
4940
+ return locatedExe(d, id).map((p) => win325.dirname(p));
4531
4941
  }
4532
4942
  function pickWorkbuddyExe(cands, d) {
4533
4943
  for (const c of cands) {
4534
- const exe = /\.exe$/i.test(c) ? c : win323.join(c, "WorkBuddy.exe");
4535
- if (win323.basename(exe).toLowerCase() === "workbuddy.exe" && d.exists(exe)) return exe;
4944
+ const exe = /\.exe$/i.test(c) ? c : win325.join(c, "WorkBuddy.exe");
4945
+ if (win325.basename(exe).toLowerCase() === "workbuddy.exe" && d.exists(exe)) return exe;
4536
4946
  }
4537
4947
  return null;
4538
4948
  }
4539
- function winWorkbuddyExeHintsAsync() {
4540
- return new Promise((resolve6) => {
4541
- try {
4542
- execFile4(
4543
- "powershell.exe",
4544
- ["-NoProfile", "-NonInteractive", "-Command", WIN_WORKBUDDY_EXE_PS],
4545
- { encoding: "utf8", windowsHide: true, timeout: 15e3 },
4546
- (err, stdout2) => resolve6(err && !stdout2 ? [] : parseExeHints(String(stdout2 ?? "")))
4547
- );
4548
- } catch {
4549
- resolve6([]);
4550
- }
4551
- });
4949
+ async function winWorkbuddyLocatorHints() {
4950
+ const p = await winAppLocator().locate("workbuddy");
4951
+ return p ? [p] : [];
4552
4952
  }
4553
- async function locateWorkbuddyAppExeAsync(d = defaultLocateDeps(), hints = winWorkbuddyExeHintsAsync) {
4953
+ async function locateWorkbuddyAppExeAsync(d = defaultLocateDeps(), hints = winWorkbuddyLocatorHints) {
4554
4954
  if (!isWin(d)) return locateWorkbuddyAppExe(d);
4555
- return pickWorkbuddyExe(winAppDirs(d, "WorkBuddy"), d) ?? pickWorkbuddyExe(await hints(), d);
4955
+ return pickWorkbuddyExe(locatedExe(d, "workbuddy"), d) ?? pickWorkbuddyExe(winAppDirs(d, "WorkBuddy"), d) ?? pickWorkbuddyExe(await hints(), d);
4556
4956
  }
4557
4957
  var isWin = (d) => (d.platform ?? process.platform) === "win32";
4558
4958
  var WIN_EXTS = [".exe", ".cmd", ".bat"];
4559
4959
  function extraBinDirs(home, nodePath = "", win) {
4560
4960
  if (win) {
4561
4961
  return [
4562
- join19(home, ".local", "bin"),
4563
- ...win.appData ? [join19(win.appData, "npm")] : [],
4564
- join19(home, ".bun", "bin"),
4565
- ...win.localAppData ? [join19(win.localAppData, "Volta", "bin")] : [],
4566
- join19(home, "scoop", "shims"),
4962
+ join20(home, ".local", "bin"),
4963
+ ...win.appData ? [join20(win.appData, "npm")] : [],
4964
+ join20(home, ".bun", "bin"),
4965
+ ...win.localAppData ? [join20(win.localAppData, "Volta", "bin")] : [],
4966
+ join20(home, "scoop", "shims"),
4567
4967
  ...nodePath ? [dirname11(nodePath)] : []
4568
4968
  ];
4569
4969
  }
4570
4970
  return [
4571
- join19(home, ".local", "bin"),
4572
- join19(home, ".claude", "local"),
4573
- join19(home, ".npm-global", "bin"),
4574
- join19(home, ".bun", "bin"),
4575
- join19(home, ".volta", "bin"),
4971
+ join20(home, ".local", "bin"),
4972
+ join20(home, ".claude", "local"),
4973
+ join20(home, ".npm-global", "bin"),
4974
+ join20(home, ".bun", "bin"),
4975
+ join20(home, ".volta", "bin"),
4576
4976
  "/opt/homebrew/bin",
4577
4977
  "/usr/local/bin",
4578
4978
  ...nodePath ? [dirname11(nodePath)] : []
@@ -4586,7 +4986,7 @@ function whichAllIn(name, d) {
4586
4986
  const out = [];
4587
4987
  for (const dir2 of binDirs(d)) {
4588
4988
  for (const ext of exts) {
4589
- const p = join19(dir2, name + ext);
4989
+ const p = join20(dir2, name + ext);
4590
4990
  if (d.isExecutable(p)) out.push(p);
4591
4991
  }
4592
4992
  }
@@ -4610,7 +5010,7 @@ function binDirs(d) {
4610
5010
  function parseNpmCmdShim(shimPath, text) {
4611
5011
  const m = text.match(/"%~?dp0%?\\?([^"%]+)"\s+%\*/i);
4612
5012
  if (!m) return null;
4613
- return join19(dirname11(shimPath), m[1].replace(/^\\/, ""));
5013
+ return join20(dirname11(shimPath), m[1].replace(/^\\/, ""));
4614
5014
  }
4615
5015
  var JS_RUNTIME_EXE = /^(?:node|bun|bunx|deno)(?:\.exe)?$/i;
4616
5016
  function resolveWinLaunch(file3, d) {
@@ -4621,7 +5021,7 @@ function resolveWinLaunch(file3, d) {
4621
5021
  const target = text ? parseNpmCmdShim(file3, text) : null;
4622
5022
  if (!target || !d.exists(target)) return null;
4623
5023
  if (/\.(?:cmd|bat)$/i.test(target)) return null;
4624
- if (JS_RUNTIME_EXE.test(win323.basename(target.replace(/\//g, "\\")))) return null;
5024
+ if (JS_RUNTIME_EXE.test(win325.basename(target.replace(/\//g, "\\")))) return null;
4625
5025
  return resolveWinLaunch(target, d);
4626
5026
  }
4627
5027
  if (/\.(?:c|m)?js$/.test(lower)) return { file: d.nodePath, prefixArgs: [file3] };
@@ -4630,22 +5030,22 @@ function resolveWinLaunch(file3, d) {
4630
5030
  return null;
4631
5031
  }
4632
5032
  function appDirs(home) {
4633
- return ["/Applications", join19(home, "Applications")];
5033
+ return ["/Applications", join20(home, "Applications")];
4634
5034
  }
4635
5035
  function winAppDirs(d, product) {
4636
5036
  return [
4637
- ...d.localAppData ? [join19(d.localAppData, "Programs", product)] : [],
4638
- join19("C:\\Program Files", product),
4639
- join19("C:\\Program Files (x86)", product)
5037
+ ...d.localAppData ? [join20(d.localAppData, "Programs", product)] : [],
5038
+ join20("C:\\Program Files", product),
5039
+ join20("C:\\Program Files (x86)", product)
4640
5040
  ];
4641
5041
  }
4642
5042
  var WORKBUDDY_CLI_IN_WIN_APP = "resources/app.asar.unpacked/cli/bin/codebuddy";
4643
- var QODER_CN_WORKER_IN_WIN_APP = "resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-cn-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
4644
- var QODER_WORKER_IN_WIN_APP = "resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
5043
+ var QODER_CN_WORKER_IN_WIN_APP = QODER_CN_WORKER_REL;
5044
+ var QODER_WORKER_IN_WIN_APP = QODER_WORKER_REL;
4645
5045
  function winClaudeExeCandidates(d) {
4646
5046
  return [
4647
- ...d.home ? [join19(d.home, ".local", "bin", "claude.exe")] : [],
4648
- ...d.appData ? [join19(d.appData, "npm", "node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe")] : []
5047
+ ...d.home ? [join20(d.home, ".local", "bin", "claude.exe")] : [],
5048
+ ...d.appData ? [join20(d.appData, "npm", "node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe")] : []
4649
5049
  ];
4650
5050
  }
4651
5051
  function winClaudeLaunches(d) {
@@ -4690,21 +5090,21 @@ function locateWin(tool, d) {
4690
5090
  case "workbuddy":
4691
5091
  return locateWorkbuddy(d)?.loc ?? null;
4692
5092
  case "qoder_cn": {
4693
- for (const dir2 of winAppDirs(d, "Qoder CN")) {
4694
- const p = join19(dir2, QODER_CN_WORKER_IN_WIN_APP);
4695
- if (d.exists(p)) return qoderWorker(d, p, join19(dir2, "Qoder CN.exe"));
5093
+ for (const dir2 of [...locatedDirs(d, "qoder_cn"), ...winAppDirs(d, "Qoder CN")]) {
5094
+ const p = join20(dir2, QODER_CN_WORKER_IN_WIN_APP);
5095
+ if (d.exists(p)) return qoderWorker(d, p, join20(dir2, "Qoder CN.exe"));
4696
5096
  }
4697
5097
  return plain("qoderclicn");
4698
5098
  }
4699
5099
  case "qoder": {
4700
- for (const dir2 of winAppDirs(d, "Qoder")) {
4701
- const p = join19(dir2, QODER_WORKER_IN_WIN_APP);
4702
- if (d.exists(p)) return qoderWorker(d, p, join19(dir2, "Qoder.exe"));
5100
+ for (const dir2 of [...locatedDirs(d, "qoder"), ...winAppDirs(d, "Qoder")]) {
5101
+ const p = join20(dir2, QODER_WORKER_IN_WIN_APP);
5102
+ if (d.exists(p)) return qoderWorker(d, p, join20(dir2, "Qoder.exe"));
4703
5103
  }
4704
5104
  return plain("qodercli");
4705
5105
  }
4706
5106
  case "opencode": {
4707
- const p = join19(d.home, ".opencode", "bin", "opencode.exe");
5107
+ const p = join20(d.home, ".opencode", "bin", "opencode.exe");
4708
5108
  if (d.exists(p)) return { file: p, prefixArgs: [] };
4709
5109
  return plain("opencode");
4710
5110
  }
@@ -4716,7 +5116,7 @@ function locateWin(tool, d) {
4716
5116
  return null;
4717
5117
  }
4718
5118
  }
4719
- function workbuddyHasTui(loc, exists = existsSync16, realpath = realpathSync2) {
5119
+ function workbuddyHasTui(loc, exists = existsSync17, realpath = realpathSync2) {
4720
5120
  const real = (p) => {
4721
5121
  try {
4722
5122
  return realpath(p);
@@ -4728,8 +5128,8 @@ function workbuddyHasTui(loc, exists = existsSync16, realpath = realpathSync2) {
4728
5128
  const cands = [...loc.prefixArgs, loc.file].map(real);
4729
5129
  const script = cands.find((a) => BIN.test(a));
4730
5130
  if (!script) return true;
4731
- const dist = join19(dirname11(dirname11(script)), "dist");
4732
- return exists(join19(dist, "codebuddy.js"));
5131
+ const dist = join20(dirname11(dirname11(script)), "dist");
5132
+ return exists(join20(dist, "codebuddy.js"));
4733
5133
  }
4734
5134
  function locateWorkbuddyTui(d = defaultLocateDeps(), exists, realpath) {
4735
5135
  const cands = [locateWorkbuddyStandalone(d), locateWorkbuddyApp(d)].filter((c) => c !== null);
@@ -4770,7 +5170,7 @@ function locateWorkbuddyStandalone(d = defaultLocateDeps()) {
4770
5170
  function locateWorkbuddyApp(d = defaultLocateDeps()) {
4771
5171
  if (!isWin(d)) {
4772
5172
  for (const dir2 of appDirs(d.home)) {
4773
- const p = join19(dir2, WORKBUDDY_CLI_IN_APP);
5173
+ const p = join20(dir2, WORKBUDDY_CLI_IN_APP);
4774
5174
  if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
4775
5175
  }
4776
5176
  return null;
@@ -4778,7 +5178,7 @@ function locateWorkbuddyApp(d = defaultLocateDeps()) {
4778
5178
  const inApp = (dirs) => {
4779
5179
  for (const dir2 of dirs) {
4780
5180
  for (const ext of [".exe", ".cmd", ""]) {
4781
- const p = join19(dir2, WORKBUDDY_CLI_IN_WIN_APP + ext);
5181
+ const p = join20(dir2, WORKBUDDY_CLI_IN_WIN_APP + ext);
4782
5182
  if (d.exists(p)) {
4783
5183
  const r = resolveWinLaunch(p, d);
4784
5184
  if (r) return r;
@@ -4787,7 +5187,7 @@ function locateWorkbuddyApp(d = defaultLocateDeps()) {
4787
5187
  }
4788
5188
  return null;
4789
5189
  };
4790
- return inApp(winAppDirs(d, "WorkBuddy")) ?? inApp(d.winAppHints?.("WorkBuddy", "WorkBuddy.exe") ?? []);
5190
+ return inApp([...locatedDirs(d, "workbuddy"), ...winAppDirs(d, "WorkBuddy")]) ?? inApp(d.winAppHints?.("WorkBuddy", "WorkBuddy.exe") ?? []);
4791
5191
  }
4792
5192
  function locateWorkbuddy(d = defaultLocateDeps()) {
4793
5193
  const s = locateWorkbuddyStandalone(d);
@@ -4813,20 +5213,20 @@ function locate(tool, d = defaultLocateDeps()) {
4813
5213
  return locateWorkbuddy(d)?.loc ?? null;
4814
5214
  case "qoder_cn": {
4815
5215
  for (const dir2 of appDirs(d.home)) {
4816
- const p = join19(dir2, QODER_CN_WORKER_IN_APP);
4817
- if (d.exists(p)) return qoderWorker(d, p, join19(dir2, "Qoder CN.app", "Contents", "MacOS", "Qoder CN"));
5216
+ const p = join20(dir2, QODER_CN_WORKER_IN_APP);
5217
+ if (d.exists(p)) return qoderWorker(d, p, join20(dir2, "Qoder CN.app", "Contents", "MacOS", "Qoder CN"));
4818
5218
  }
4819
5219
  return plain("qoderclicn");
4820
5220
  }
4821
5221
  case "qoder": {
4822
5222
  for (const dir2 of appDirs(d.home)) {
4823
- const p = join19(dir2, QODER_WORKER_IN_APP);
4824
- if (d.exists(p)) return qoderWorker(d, p, join19(dir2, "Qoder.app", "Contents", "MacOS", "Qoder"));
5223
+ const p = join20(dir2, QODER_WORKER_IN_APP);
5224
+ if (d.exists(p)) return qoderWorker(d, p, join20(dir2, "Qoder.app", "Contents", "MacOS", "Qoder"));
4825
5225
  }
4826
5226
  return plain("qodercli");
4827
5227
  }
4828
5228
  case "opencode": {
4829
- const p = join19(d.home, ".opencode", "bin", "opencode");
5229
+ const p = join20(d.home, ".opencode", "bin", "opencode");
4830
5230
  if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
4831
5231
  return plain("opencode");
4832
5232
  }
@@ -4847,7 +5247,7 @@ function findWt(d = defaultLocateDeps()) {
4847
5247
  if (!isWin(d)) return null;
4848
5248
  const hit = whichIn("wt", d);
4849
5249
  if (hit) return hit;
4850
- const alias = d.localAppData ? join19(d.localAppData, "Microsoft", "WindowsApps", "wt.exe") : "";
5250
+ const alias = d.localAppData ? join20(d.localAppData, "Microsoft", "WindowsApps", "wt.exe") : "";
4851
5251
  return alias && (d.exists(alias) || (d.lexists?.(alias) ?? false)) ? alias : null;
4852
5252
  }
4853
5253
 
@@ -4914,13 +5314,13 @@ function findWbTranscript(configDir, sessionId) {
4914
5314
  if (!SESSION_UUID_RE.test(sessionId)) return null;
4915
5315
  let dirs;
4916
5316
  try {
4917
- dirs = readdirSync8(join20(configDir, "projects"));
5317
+ dirs = readdirSync9(join21(configDir, "projects"));
4918
5318
  } catch {
4919
5319
  return null;
4920
5320
  }
4921
5321
  for (const d of dirs) {
4922
- const p = join20(configDir, "projects", d, `${sessionId}.jsonl`);
4923
- if (existsSync17(p)) return p;
5322
+ const p = join21(configDir, "projects", d, `${sessionId}.jsonl`);
5323
+ if (existsSync18(p)) return p;
4924
5324
  }
4925
5325
  return null;
4926
5326
  }
@@ -4928,7 +5328,7 @@ var SLICE = 64 * 1024;
4928
5328
  function readSlice(p, from) {
4929
5329
  const fd = openSync5(p, "r");
4930
5330
  try {
4931
- const size = statSync8(p).size;
5331
+ const size = statSync9(p).size;
4932
5332
  const len = Math.min(size, SLICE);
4933
5333
  const buf = Buffer.alloc(len);
4934
5334
  readSync5(fd, buf, 0, len, from === "head" ? 0 : size - len);
@@ -4972,7 +5372,7 @@ function userText2(o) {
4972
5372
  function wbTranscriptInfo(p) {
4973
5373
  const head = jsonLines(readSlice(p, "head"));
4974
5374
  const tail = jsonLines(readSlice(p, "tail"));
4975
- const mtime = Math.floor(statSync8(p).mtimeMs);
5375
+ const mtime = Math.floor(statSync9(p).mtimeMs);
4976
5376
  const hs = head.map((o) => tsOf(o.timestamp)).filter((n) => n !== null);
4977
5377
  const ts = tail.map((o) => tsOf(o.timestamp)).filter((n) => n !== null);
4978
5378
  const createdAt = hs.length ? Math.min(...hs) : mtime;
@@ -5005,8 +5405,8 @@ function wbElectronCandidates(appCliPath) {
5005
5405
  const m = /^(.*?)[\\/]resources[\\/]app\.asar\.unpacked[\\/]/i.exec(appCliPath);
5006
5406
  if (!m) return [];
5007
5407
  const root = m[1];
5008
- if (/\.app[\\/]Contents$/.test(root)) return [join20(root, "MacOS", "Electron"), join20(root, "MacOS", "WorkBuddy")];
5009
- return [join20(root, "WorkBuddy.exe")];
5408
+ if (/\.app[\\/]Contents$/.test(root)) return [join21(root, "MacOS", "Electron"), join21(root, "MacOS", "WorkBuddy")];
5409
+ return [join21(root, "WorkBuddy.exe")];
5010
5410
  }
5011
5411
  function defaultWbElectron(d = defaultLocateDeps()) {
5012
5412
  if ((d.platform ?? process.platform) === "win32") return null;
@@ -5049,11 +5449,11 @@ function defaultAppRunning(platform = process.platform) {
5049
5449
  var PENDING_MAX = 20;
5050
5450
  var PENDING_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
5051
5451
  function pendingPath() {
5052
- return join20(deskbuddyHome(), "workbuddy-pending.json");
5452
+ return join21(deskbuddyHome(), "workbuddy-pending.json");
5053
5453
  }
5054
5454
  function loadPendingFile() {
5055
5455
  try {
5056
- const v = JSON.parse(readFileSync21(pendingPath(), "utf8"));
5456
+ const v = JSON.parse(readFileSync23(pendingPath(), "utf8"));
5057
5457
  return Array.isArray(v) ? v.filter((j) => j && typeof j.sessionId === "string" && typeof j.cwd === "string") : [];
5058
5458
  } catch {
5059
5459
  return [];
@@ -5065,10 +5465,10 @@ function savePendingFile(jobs) {
5065
5465
  rmSync12(pendingPath(), { force: true });
5066
5466
  return;
5067
5467
  }
5068
- mkdirSync19(deskbuddyHome(), { recursive: true });
5468
+ mkdirSync20(deskbuddyHome(), { recursive: true });
5069
5469
  const tmp = pendingPath() + ".tmp";
5070
- writeFileSync19(tmp, JSON.stringify(jobs), { mode: 384 });
5071
- renameSync13(tmp, pendingPath());
5470
+ writeFileSync20(tmp, JSON.stringify(jobs), { mode: 384 });
5471
+ renameSync14(tmp, pendingPath());
5072
5472
  } catch {
5073
5473
  }
5074
5474
  }
@@ -5173,7 +5573,7 @@ async function registerNow(job, d) {
5173
5573
  if (!SESSION_UUID_RE.test(job.sessionId)) return "skip bad_session";
5174
5574
  if (!job.cwd || !isAbsolute2(job.cwd)) return "skip bad_cwd";
5175
5575
  const db = d.dbPath();
5176
- if (!db || !existsSync17(db)) return "skip no_db";
5576
+ if (!db || !existsSync18(db)) return "skip no_db";
5177
5577
  const tp = findWbTranscript(d.configDir(), job.sessionId);
5178
5578
  if (!tp) return "skip no_transcript";
5179
5579
  const info = wbTranscriptInfo(tp);
@@ -5305,7 +5705,7 @@ var WsCdpClient = class {
5305
5705
  };
5306
5706
 
5307
5707
  // src/daemon/workbuddyApp/keeper.ts
5308
- import { posix as posix2, win32 as win324 } from "path";
5708
+ import { posix as posix2, win32 as win326 } from "path";
5309
5709
  var DEFAULT_CDP_PORT = 39217;
5310
5710
  var ENV_NAME = "WORKBUDDY_REMOTE_DEBUGGING_PORT";
5311
5711
  var RESTART_GAP_MS = 30 * 60 * 1e3;
@@ -5367,21 +5767,21 @@ function cleanLaunchEnv(src = process.env, shim = shimDir()) {
5367
5767
  }
5368
5768
  var REMEMBER_EXE_MS = 30 * 60 * 1e3;
5369
5769
  var AWAY_IDLE_MS = 5 * 60 * 1e3;
5370
- var keeperThrottleFile = () => join21(deskbuddyHome(), "wbapp", "keeper.json");
5770
+ var keeperThrottleFile = () => join22(deskbuddyHome(), "wbapp", "keeper.json");
5371
5771
  var filePersist = {
5372
5772
  load: () => {
5373
5773
  try {
5374
- return JSON.parse(readFileSync22(keeperThrottleFile(), "utf8"));
5774
+ return JSON.parse(readFileSync24(keeperThrottleFile(), "utf8"));
5375
5775
  } catch {
5376
5776
  return null;
5377
5777
  }
5378
5778
  },
5379
5779
  save: (s) => {
5380
5780
  try {
5381
- mkdirSync20(join21(deskbuddyHome(), "wbapp"), { recursive: true });
5781
+ mkdirSync21(join22(deskbuddyHome(), "wbapp"), { recursive: true });
5382
5782
  const f = keeperThrottleFile();
5383
- writeFileSync20(f + ".tmp", JSON.stringify(s));
5384
- renameSync14(f + ".tmp", f);
5783
+ writeFileSync21(f + ".tmp", JSON.stringify(s));
5784
+ renameSync15(f + ".tmp", f);
5385
5785
  } catch {
5386
5786
  }
5387
5787
  }
@@ -5413,7 +5813,7 @@ function defaultWorkbuddyAppPath() {
5413
5813
  }
5414
5814
  function isValidAppPath(p, platform = process.platform) {
5415
5815
  if (typeof p !== "string" || !p) return false;
5416
- if (platform === "win32") return win324.isAbsolute(p) && /^[a-z]:[\\/]/i.test(p) && win324.basename(p).toLowerCase() === "workbuddy.exe";
5816
+ if (platform === "win32") return win326.isAbsolute(p) && /^[a-z]:[\\/]/i.test(p) && win326.basename(p).toLowerCase() === "workbuddy.exe";
5417
5817
  return posix2.isAbsolute(p) && posix2.basename(p) === "WorkBuddy.app";
5418
5818
  }
5419
5819
  var HUNG_RESTART_MS = 20 * 60 * 1e3;
@@ -5561,11 +5961,11 @@ function winMainProc(procs) {
5561
5961
  return null;
5562
5962
  }
5563
5963
  function workbuddyBusy(now = Date.now(), dir2 = stateDir(), alive = isAlive) {
5564
- if (!existsSync18(dir2)) return false;
5565
- for (const f of readdirSync9(dir2)) {
5964
+ if (!existsSync19(dir2)) return false;
5965
+ for (const f of readdirSync10(dir2)) {
5566
5966
  if (!f.endsWith(".json")) continue;
5567
5967
  try {
5568
- const s = JSON.parse(readFileSync22(join21(dir2, f), "utf8"));
5968
+ const s = JSON.parse(readFileSync24(join22(dir2, f), "utf8"));
5569
5969
  if (s.tool !== "workbuddy" || s.state !== "watch" && s.state !== "ask_approval") continue;
5570
5970
  if (typeof s.pid === "number" && s.pid > 0) {
5571
5971
  if (alive(s.pid, s.pidName)) return true;
@@ -5883,7 +6283,7 @@ var CdpKeeper = class {
5883
6283
  this.#loadThrottle();
5884
6284
  let path = null;
5885
6285
  try {
5886
- const exists = this.#d.exists ?? existsSync18;
6286
+ const exists = this.#d.exists ?? existsSync19;
5887
6287
  if (this.#appExe && isValidAppPath(this.#appExe, this.#platform()) && exists(this.#appExe)) path = this.#appExe;
5888
6288
  else {
5889
6289
  const found = await (this.#d.appPath ? this.#d.appPath() : defaultWorkbuddyAppPath());
@@ -6099,12 +6499,12 @@ var TYPE_TEXT = "text";
6099
6499
  var TYPE_SUMMARY = "summary";
6100
6500
  var MAX_TEXT_LENGTH = 2e3;
6101
6501
  var MAX_ID_LENGTH = 64;
6102
- function str(v) {
6502
+ function str2(v) {
6103
6503
  return typeof v === "string" ? v : "";
6104
6504
  }
6105
6505
  function parseMillis(v) {
6106
6506
  if (typeof v === "number" && Number.isFinite(v)) return v;
6107
- const s = str(v).trim();
6507
+ const s = str2(v).trim();
6108
6508
  if (!/^\d{1,16}$/.test(s)) return 0;
6109
6509
  const n = Number(s);
6110
6510
  return Number.isSafeInteger(n) ? n : 0;
@@ -6117,21 +6517,21 @@ function sanitizeText(raw) {
6117
6517
  }
6118
6518
  function parseCommand(frame) {
6119
6519
  const f = frame;
6120
- if (!f || str(f.service) !== SERVICE_COMMAND) return null;
6520
+ if (!f || str2(f.service) !== SERVICE_COMMAND) return null;
6121
6521
  const d = f.data;
6122
6522
  if (!d || typeof d !== "object") return null;
6123
- const instructionId = str(d.instructionId);
6523
+ const instructionId = str2(d.instructionId);
6124
6524
  if (!isValidId(instructionId)) return null;
6125
- const type = str(d.type);
6525
+ const type = str2(d.type);
6126
6526
  if (type !== TYPE_TEXT && type !== TYPE_SUMMARY) return null;
6127
- const tool = str(d.tool);
6527
+ const tool = str2(d.tool);
6128
6528
  if (!isValidId(tool)) return null;
6129
- const sessionId = str(d.sessionId);
6529
+ const sessionId = str2(d.sessionId);
6130
6530
  if (sessionId && !isValidId(sessionId)) return null;
6131
6531
  if (type === TYPE_SUMMARY && !sessionId) return null;
6132
- const text = sanitizeText(str(d.text));
6532
+ const text = sanitizeText(str2(d.text));
6133
6533
  if (!text || text.length > MAX_TEXT_LENGTH) return null;
6134
- const rawDir = str(d.dirRef);
6534
+ const rawDir = str2(d.dirRef);
6135
6535
  const dirRef = !sessionId && /^[0-9a-f]{16}$/.test(rawDir) ? rawDir : "";
6136
6536
  const cmd = { instructionId, type, sessionId, tool, text, expireAt: parseMillis(d.expireAt), dirRef };
6137
6537
  if (type === TYPE_SUMMARY) cmd.kind = "summary";
@@ -6151,14 +6551,14 @@ function cleanFeedback(v) {
6151
6551
  }
6152
6552
  function parseApprovalFrame(frame) {
6153
6553
  const f = frame;
6154
- if (!f || str(f.service) !== SERVICE_COMMAND) return null;
6554
+ if (!f || str2(f.service) !== SERVICE_COMMAND) return null;
6155
6555
  const d = f.data;
6156
- if (!d || typeof d !== "object" || str(d.type) !== TYPE_APPROVAL) return null;
6157
- const instructionId = str(d.instructionId);
6556
+ if (!d || typeof d !== "object" || str2(d.type) !== TYPE_APPROVAL) return null;
6557
+ const instructionId = str2(d.instructionId);
6158
6558
  if (!isValidId(instructionId)) return null;
6159
- const approvalId = str(d.approvalId);
6559
+ const approvalId = str2(d.approvalId);
6160
6560
  if (!isValidId(approvalId)) return null;
6161
- const decision = str(d.decision);
6561
+ const decision = str2(d.decision);
6162
6562
  if (decision !== "allow" && decision !== "deny" && decision !== "allow_always") return null;
6163
6563
  return {
6164
6564
  instructionId,
@@ -6178,6 +6578,11 @@ function optionFields(d) {
6178
6578
  function cleanDeviceName(v) {
6179
6579
  return typeof v === "string" ? v.replace(/[\u0000-\u001f\u007f]/g, "").trim().slice(0, 32) : "";
6180
6580
  }
6581
+ var APP_WAIT_MARGIN_MS = 20 * 1e3;
6582
+ function appWaitBudget(expireAt, now, max) {
6583
+ if (!(expireAt > 0)) return max;
6584
+ return Math.min(max, expireAt - now - APP_WAIT_MARGIN_MS);
6585
+ }
6181
6586
  function isExpired(cmd, now = Date.now()) {
6182
6587
  return cmd.expireAt > 0 && now > cmd.expireAt;
6183
6588
  }
@@ -6408,6 +6813,12 @@ var QoderAppTasks = class {
6408
6813
  async #ensureReady(cmd, sid, q, report, mayWake) {
6409
6814
  if (this.available()) return true;
6410
6815
  if (!mayWake || !this.#d.wake || !this.canWake()) return false;
6816
+ const raw = appWaitBudget(cmd.expireAt, this.#d.now(), QODER_APP_WAKE_WAIT_MS);
6817
+ const budget = Math.max(0, raw);
6818
+ if (raw <= 0 && !sid) {
6819
+ this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} \u79BB\u8FC7\u671F\u4E0D\u5230 ${APP_WAIT_MARGIN_MS / 1e3} \u79D2\uFF0C\u4E0D\u7B49 ${this.#d.label} App\uFF0C\u76F4\u63A5\u56DE\u843D`);
6820
+ return false;
6821
+ }
6411
6822
  let w;
6412
6823
  q.wakeAt = this.#d.now();
6413
6824
  try {
@@ -6425,13 +6836,13 @@ var QoderAppTasks = class {
6425
6836
  this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} \u7B49 ${this.#d.label} App ${w === "launched" ? "\u6253\u5F00" : w === "restarted" ? "\u5E26\u7AEF\u53E3\u91CD\u542F" : "\u8FDE\u4E0A"}`);
6426
6837
  report("queued", sid);
6427
6838
  }
6428
- const end = this.#d.now() + QODER_APP_WAKE_WAIT_MS;
6839
+ const end = q.wakeAt + budget;
6429
6840
  while (this.#d.now() < end) {
6430
6841
  this.#d.kick?.();
6431
- await this.#sleep(this.#d.pollMs ?? QODER_APP_POLL_MS);
6842
+ await this.#sleep(Math.max(1, Math.min(this.#d.pollMs ?? QODER_APP_POLL_MS, end - this.#d.now())));
6432
6843
  if (this.available()) return true;
6433
6844
  }
6434
- this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} ${this.#d.label} App ${QODER_APP_WAKE_WAIT_MS / 1e3} \u79D2\u5185\u6CA1\u8FDE\u4E0A`);
6845
+ this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} ${this.#d.label} App ${Math.round(budget / 1e3)} \u79D2\u5185\u6CA1\u8FDE\u4E0A${budget < QODER_APP_WAKE_WAIT_MS ? "\uFF08\u518D\u7B49\u6307\u4EE4\u5C31\u8FC7\u671F\u4E86\uFF09" : ""}`);
6435
6846
  return false;
6436
6847
  }
6437
6848
  /** 只给测试和诊断用 */
@@ -6719,6 +7130,149 @@ var QoderAppTasks = class {
6719
7130
  }
6720
7131
  };
6721
7132
 
7133
+ // src/core/summaryTools.ts
7134
+ var SUMMARY_TOOLS = ["claude_code", "codex", "qoder", "qoder_cn", "opencode", "workbuddy", "qwenwork", "dumate", "deepseek_harness"];
7135
+ var SUMMARY_CAPS = SUMMARY_TOOLS.map((t) => `summary:${t}`);
7136
+
7137
+ // src/daemon/summary.ts
7138
+ var SUMMARY_TOOLS2 = SUMMARY_TOOLS;
7139
+ var SUMMARY_NO_OUTPUT = "no_output";
7140
+ var SUMMARY_TIMEOUT = "timeout";
7141
+ function isSummary(cmd) {
7142
+ return cmd.kind === "summary";
7143
+ }
7144
+ function summaryMark(instructionId) {
7145
+ return `[DeskBuddy#${instructionId}]`;
7146
+ }
7147
+ function withSummaryMark(text, instructionId) {
7148
+ if (text.includes(`[DeskBuddy#${instructionId}]`) || text.includes(`[DeskBuddy#${instructionId}:`)) return text;
7149
+ return `${text}
7150
+ ${summaryMark(instructionId)}`;
7151
+ }
7152
+ function clipOutput(text) {
7153
+ return text.length > COMMAND_OUTPUT_MAX ? text.slice(0, COMMAND_OUTPUT_MAX) : text;
7154
+ }
7155
+ var BUSY = /* @__PURE__ */ new Set(["watch", "ask_approval"]);
7156
+ var PTY_SETTLE_MS = 1500;
7157
+ var SUMMARY_WAIT_MS = 4 * 60 * 1e3;
7158
+ var PTY_SUMMARY_WAIT_MS = SUMMARY_WAIT_MS;
7159
+ function ptyVerdict(job, st, tail, now) {
7160
+ if (now - job.startedAt > PTY_SUMMARY_WAIT_MS) return { kind: "fail", reason: SUMMARY_TIMEOUT };
7161
+ if (!st) return { kind: "fail", reason: "session_gone" };
7162
+ if (!tail || tail.id !== job.instructionId) return { kind: "wait" };
7163
+ if (tail.humanAfter || tail.interrupted) return { kind: "fail", reason: "exec_failed" };
7164
+ if (!tail.answered) return { kind: "wait" };
7165
+ if (BUSY.has(st.state) || now - st.updatedAt < PTY_SETTLE_MS) return { kind: "wait" };
7166
+ const text = (tail.reply ?? "").trim();
7167
+ return text ? { kind: "done", text } : { kind: "fail", reason: SUMMARY_NO_OUTPUT };
7168
+ }
7169
+ function hasSummaryMark(text, instructionId) {
7170
+ return text.includes(`[DeskBuddy#${instructionId}]`) || text.includes(`[DeskBuddy#${instructionId}:`);
7171
+ }
7172
+ function messagesVerdict(list, instructionId, idle) {
7173
+ if (!Array.isArray(list)) return { kind: "wait" };
7174
+ const msgs = list;
7175
+ const textOf = (m) => Array.isArray(m.parts) ? m.parts.filter((p) => p && p.type === "text" && typeof p.text === "string" && p.synthetic !== true).map((p) => p.text).join("\n").trim() : "";
7176
+ let at = -1;
7177
+ for (let i = msgs.length - 1; i >= 0; i--) {
7178
+ const m = msgs[i];
7179
+ if (m && m.info?.role === "user" && hasSummaryMark(textOf(m), instructionId)) {
7180
+ at = i;
7181
+ break;
7182
+ }
7183
+ }
7184
+ if (at < 0) return { kind: "wait" };
7185
+ const after = msgs.slice(at + 1).filter((m) => m && m.info);
7186
+ if (after.some((m) => m.info?.role === "user")) return { kind: "fail", reason: "exec_failed" };
7187
+ const markId = typeof msgs[at].info?.id === "string" ? msgs[at].info?.id : "";
7188
+ const replies = after.filter((m) => m.info?.role === "assistant" && (!markId || typeof m.info.parentID !== "string" || m.info.parentID === markId));
7189
+ if (!idle || replies.length === 0) return { kind: "wait" };
7190
+ const last = replies[replies.length - 1];
7191
+ if (!last.info?.time || last.info.time.completed == null) return { kind: "wait" };
7192
+ for (let i = replies.length - 1; i >= 0; i--) {
7193
+ const t = textOf(replies[i]);
7194
+ if (t) return { kind: "done", text: t };
7195
+ }
7196
+ return { kind: "fail", reason: SUMMARY_NO_OUTPUT };
7197
+ }
7198
+ function replyVerdict(r) {
7199
+ if (!r || typeof r !== "object") return { kind: "wait" };
7200
+ const v = r;
7201
+ if (v.ok === true) {
7202
+ const t = typeof v.text === "string" ? v.text.trim() : "";
7203
+ return t ? { kind: "done", text: t } : { kind: "fail", reason: SUMMARY_NO_OUTPUT };
7204
+ }
7205
+ const reason = typeof v.reason === "string" && /^[a-z_]{1,32}$/.test(v.reason) ? v.reason : "exec_failed";
7206
+ return { kind: "fail", reason };
7207
+ }
7208
+ var PtySummaryWatch = class {
7209
+ #d;
7210
+ #jobs = /* @__PURE__ */ new Map();
7211
+ constructor(deps) {
7212
+ this.#d = deps;
7213
+ }
7214
+ get size() {
7215
+ return this.#jobs.size;
7216
+ }
7217
+ has(instructionId) {
7218
+ return this.#jobs.has(instructionId);
7219
+ }
7220
+ /** 交出去(started / queued)时登记;handedAt = 交出去的时刻(缺省现在)。同一条重复登记保留最早的时刻 */
7221
+ begin(cmd, sessionId, handedAt, check) {
7222
+ const prev = this.#jobs.get(cmd.instructionId);
7223
+ this.#jobs.set(cmd.instructionId, { cmd, sessionId, startedAt: prev?.startedAt ?? handedAt ?? this.#d.now(), check: check ?? prev?.check });
7224
+ }
7225
+ drop(instructionId) {
7226
+ this.#jobs.delete(instructionId);
7227
+ }
7228
+ #settle(id, job, v) {
7229
+ if (v.kind === "wait" || this.#jobs.get(id) !== job) return;
7230
+ this.#jobs.delete(id);
7231
+ if (v.kind === "done") this.#d.done(job.cmd, job.sessionId, v.text);
7232
+ else this.#d.fail(job.cmd, job.sessionId, v.reason);
7233
+ }
7234
+ tick() {
7235
+ const now = this.#d.now();
7236
+ for (const [id, job] of [...this.#jobs]) {
7237
+ if (job.check) {
7238
+ if (now - job.startedAt > PTY_SUMMARY_WAIT_MS) {
7239
+ this.#settle(id, job, { kind: "fail", reason: SUMMARY_TIMEOUT });
7240
+ continue;
7241
+ }
7242
+ if (job.checking) continue;
7243
+ let r;
7244
+ try {
7245
+ r = job.check({ instructionId: id, sessionId: job.sessionId, startedAt: job.startedAt }, now);
7246
+ } catch {
7247
+ continue;
7248
+ }
7249
+ if (r && typeof r.then === "function") {
7250
+ job.checking = true;
7251
+ void r.then(
7252
+ (v) => {
7253
+ job.checking = false;
7254
+ this.#settle(id, job, v);
7255
+ },
7256
+ () => {
7257
+ job.checking = false;
7258
+ }
7259
+ );
7260
+ } else this.#settle(id, job, r);
7261
+ continue;
7262
+ }
7263
+ let st = null;
7264
+ let tail = null;
7265
+ try {
7266
+ st = this.#d.readState(job.sessionId);
7267
+ const path = st ? this.#d.transcriptOf(st) : null;
7268
+ tail = path ? this.#d.analyze(path) : null;
7269
+ } catch {
7270
+ }
7271
+ this.#settle(id, job, ptyVerdict({ instructionId: id, startedAt: job.startedAt }, st, tail, now));
7272
+ }
7273
+ }
7274
+ };
7275
+
6722
7276
  // src/daemon/dumateWatch.ts
6723
7277
  var APP_NOT_RUNNING = "app_not_running";
6724
7278
  function unavailableMessage(reason) {
@@ -6772,6 +7326,7 @@ function isRefused(e) {
6772
7326
  return code(e) === "ECONNREFUSED" || code(e?.cause) === "ECONNREFUSED";
6773
7327
  }
6774
7328
  var REPLY_HISTORY_LIMIT = 10;
7329
+ var SUMMARY_HISTORY_LIMIT = 100;
6775
7330
  function pickOfInfo(info) {
6776
7331
  const i = info && typeof info === "object" ? info : null;
6777
7332
  if (!i || i.role !== "user") return null;
@@ -6918,17 +7473,17 @@ var hasArg = (cc) => Object.values(cc.tool_input).some((v) => typeof v === "stri
6918
7473
  function dumateAskCc(p, call2) {
6919
7474
  if (call2 && hasArg(call2.cc)) return call2.cc;
6920
7475
  const md = p.metadata && typeof p.metadata === "object" ? p.metadata : {};
6921
- const str2 = (v) => typeof v === "string" ? v : "";
6922
- const file3 = str2(md.filepath) || str2(md.filePath) || str2(md.hostPath);
6923
- const dir2 = str2(md.parentDir) || str2(md.hostParentDir);
7476
+ const str3 = (v) => typeof v === "string" ? v : "";
7477
+ const file3 = str3(md.filepath) || str3(md.filePath) || str3(md.hostPath);
7478
+ const dir2 = str3(md.parentDir) || str3(md.hostParentDir);
6924
7479
  const patterns = Array.isArray(p.patterns) ? p.patterns.filter((x) => typeof x === "string").join(" ") : "";
6925
7480
  if (call2 && call2.tool) {
6926
- const cc = toCcTool(call2.tool, { filePath: file3, command: str2(md.command), path: dir2 || patterns, pattern: str2(md.pattern), url: str2(md.url) });
7481
+ const cc = toCcTool(call2.tool, { filePath: file3, command: str3(md.command), path: dir2 || patterns, pattern: str3(md.pattern), url: str3(md.url) });
6927
7482
  if (hasArg(cc)) return cc;
6928
7483
  }
6929
7484
  if (p.permission === "external_directory") {
6930
7485
  const display = p.display && typeof p.display === "object" ? p.display : {};
6931
- return { tool_name: str2(display.title) || "\u8BBF\u95EE\u5DE5\u4F5C\u533A\u5916\u76EE\u5F55", tool_input: { path: file3 || dir2 || patterns } };
7486
+ return { tool_name: str3(display.title) || "\u8BBF\u95EE\u5DE5\u4F5C\u533A\u5916\u76EE\u5F55", tool_input: { path: file3 || dir2 || patterns } };
6932
7487
  }
6933
7488
  return askToCc(p);
6934
7489
  }
@@ -6936,7 +7491,7 @@ function scanSessionDirs(root, now, maxAgeMs = ACTIVE_DIR_MS) {
6936
7491
  const out = [];
6937
7492
  const list = (p) => {
6938
7493
  try {
6939
- return readdirSync10(p, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join22(p, d.name));
7494
+ return readdirSync11(p, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join23(p, d.name));
6940
7495
  } catch {
6941
7496
  return [];
6942
7497
  }
@@ -6945,7 +7500,7 @@ function scanSessionDirs(root, now, maxAgeMs = ACTIVE_DIR_MS) {
6945
7500
  for (const day of list(u).sort().reverse().slice(0, 2))
6946
7501
  for (const dir2 of list(day)) {
6947
7502
  try {
6948
- const at = statSync9(dir2).mtimeMs;
7503
+ const at = statSync10(dir2).mtimeMs;
6949
7504
  if (now - at <= maxAgeMs) out.push({ dir: dir2, at });
6950
7505
  } catch {
6951
7506
  }
@@ -7088,14 +7643,14 @@ var DumateWatch = class {
7088
7643
  return this.#d.alt.subscribe;
7089
7644
  }
7090
7645
  /** 设备新建 / 回复用的目标:可用就用;不可用按规则唤起并等连上(ensure);还是不行返回原因码 */
7091
- async #ensureTarget() {
7646
+ async #ensureTarget(expireAt = 0) {
7092
7647
  const pick = () => this.#discover(false);
7093
7648
  let t = this.#target ?? pick();
7094
7649
  if (t) return t;
7095
7650
  let why = APP_NOT_RUNNING;
7096
7651
  if (this.#d.ensure) {
7097
7652
  try {
7098
- why = await this.#d.ensure();
7653
+ why = await this.#d.ensure(expireAt);
7099
7654
  } catch {
7100
7655
  why = APP_NOT_RUNNING;
7101
7656
  }
@@ -7110,12 +7665,12 @@ var DumateWatch = class {
7110
7665
  * model / agent 取法同回复:没有历史,直接 GET /config 的 model + agent='build'。
7111
7666
  * 幂等:同一 instructionId 在本进程里只建一次(跨重启靠 daemon 的 seen 去重);/session 不收客户端指定 id,所以不做确定性 id
7112
7667
  */
7113
- async startTask(cwd, text, instructionId) {
7668
+ async startTask(cwd, text, instructionId, expireAt = 0) {
7114
7669
  const notSent = (reason, message) => ({ status: "not_sent", reason, message });
7115
7670
  if (this.#d.enabled && !this.#d.enabled()) return notSent("tool_unsupported", "\u767E\u5EA6\u642D\u5B50\u6CA1\u6709\u63A5\u5165");
7116
7671
  const existing = instructionId ? this.#newTasks.get(instructionId) : void 0;
7117
7672
  if (existing) return { status: "started", sessionId: existing };
7118
- const got = await this.#ensureTarget();
7673
+ const got = await this.#ensureTarget(expireAt);
7119
7674
  if (typeof got === "string") return notSent(got, unavailableMessage(got));
7120
7675
  const t = got;
7121
7676
  const http = this.#http(t);
@@ -7144,15 +7699,34 @@ var DumateWatch = class {
7144
7699
  if (r.status >= 400 && r.status < 500) return { status: "failed", reason: "window_failed", message: `\u5EFA\u4E86\u4F1A\u8BDD\u4F46\u7B2C\u4E00\u53E5\u88AB\u62D2\uFF08HTTP ${r.status}\uFF09`, sessionId: sid };
7145
7700
  return { status: "failed", reason: "window_failed", message: r.status === 0 ? "\u5EFA\u4E86\u4F1A\u8BDD\u4F46\u53D1\u7B2C\u4E00\u53E5\u65F6\u8FDE\u63A5\u65AD\u5F00\u6216\u8D85\u65F6" : `\u5EFA\u4E86\u4F1A\u8BDD\u4F46\u767E\u5EA6\u642D\u5B50\u51FA\u9519\uFF08HTTP ${r.status}\uFF09`, sessionId: sid };
7146
7701
  }
7702
+ /**
7703
+ * 任务总结的读回(spec 2026-10-11 summary-all-tools):GET /session/:id/message + /session/status,按标记认本轮(messagesVerdict)。
7704
+ * 连不上 / 读不出 → 等(总超时由调用方判);不唤起 App
7705
+ */
7706
+ async summaryCheck(sessionId, instructionId) {
7707
+ const wait = { kind: "wait" };
7708
+ const t = this.#target ?? this.#discover(false);
7709
+ if (!t) return wait;
7710
+ const http = this.#http(t);
7711
+ const dir2 = this.#sessionDir.get(sessionId) ?? "";
7712
+ const sid = encodeURIComponent(sessionId);
7713
+ const msgs = await http(t, { method: "GET", path: `/session/${sid}/message?limit=${SUMMARY_HISTORY_LIMIT}`, ...dir2 ? { directory: dir2 } : {} });
7714
+ if (msgs.status === 404) return { kind: "fail", reason: "session_gone" };
7715
+ if (msgs.status !== 200) return wait;
7716
+ const st = await http(t, { method: "GET", path: "/session/status", ...dir2 ? { directory: dir2 } : {} });
7717
+ if (st.status !== 200 || !st.body || typeof st.body !== "object") return wait;
7718
+ const type = st.body[sessionId]?.type;
7719
+ return messagesVerdict(msgs.body, instructionId, type !== "busy" && type !== "preparing" && type !== "retry");
7720
+ }
7147
7721
  /**
7148
7722
  * 设备回复:往百度搭子里这个会话发一轮(spec 2026-10-08 dumate-qwenwork-reply §3.1)。界面订着同一个 serve 的事件流,实时显示(10-08 实测)。
7149
7723
  * 请求体与界面自己发消息一致:带这个会话最后一条用户消息的 agent / model(没有就 build + /config 的 model)。
7150
7724
  * 先查 /session/status 判忙闲(busy / preparing / retry → queued),再 POST prompt_async(带会话目录)
7151
7725
  */
7152
- async deliverReply(sessionId, text) {
7726
+ async deliverReply(sessionId, text, expireAt = 0) {
7153
7727
  const notSent = (reason, message) => ({ status: "not_sent", reason, message });
7154
7728
  if (this.#d.enabled && !this.#d.enabled()) return notSent("tool_unsupported", "\u767E\u5EA6\u642D\u5B50\u6CA1\u6709\u63A5\u5165");
7155
- const got = await this.#ensureTarget();
7729
+ const got = await this.#ensureTarget(expireAt);
7156
7730
  if (typeof got === "string") return notSent(got, unavailableMessage(got));
7157
7731
  const t = got;
7158
7732
  const http = this.#http(t);
@@ -7567,7 +8141,7 @@ var DumateWatch = class {
7567
8141
  const now = this.#now();
7568
8142
  if (now - this.#scanAt >= SCAN_DIRS_MS) {
7569
8143
  this.#scanAt = now;
7570
- for (const d of (this.#d.sessionDirs ?? (() => scanSessionDirs(join22(qianfanHome(), "workspace", "sessions"), now)))()) {
8144
+ for (const d of (this.#d.sessionDirs ?? (() => scanSessionDirs(join23(qianfanHome(), "workspace", "sessions"), now)))()) {
7571
8145
  if (!this.#dirs.has(dirKey(d))) this.#touchDir(d, now);
7572
8146
  }
7573
8147
  }
@@ -7679,9 +8253,9 @@ function runHook(node, input, agentPid, tool = "dumate") {
7679
8253
  }
7680
8254
 
7681
8255
  // src/daemon/qoderApp/keeper.ts
7682
- import { existsSync as existsSync19, mkdirSync as mkdirSync21, readdirSync as readdirSync11, readFileSync as readFileSync23, renameSync as renameSync15, writeFileSync as writeFileSync21 } from "fs";
8256
+ import { existsSync as existsSync20, mkdirSync as mkdirSync22, readdirSync as readdirSync12, readFileSync as readFileSync25, renameSync as renameSync16, writeFileSync as writeFileSync22 } from "fs";
7683
8257
  import { homedir as homedir7 } from "os";
7684
- import { join as join23, win32 as win325 } from "path";
8258
+ import { join as join24, win32 as win327 } from "path";
7685
8259
  var WAKE_LAUNCH_GAP_MS2 = 60 * 1e3;
7686
8260
  var RESTART_GAP_MS2 = 30 * 60 * 1e3;
7687
8261
  var RESTART_DAILY_MAX2 = 3;
@@ -7693,7 +8267,7 @@ function winBrandMainProc(procs, exe) {
7693
8267
  for (const p of procs) {
7694
8268
  const argv = winSplitArgs(p.cmdline);
7695
8269
  const first = argv[0] ?? "";
7696
- if (win325.basename(first).toLowerCase() !== want) continue;
8270
+ if (win327.basename(first).toLowerCase() !== want) continue;
7697
8271
  const rest = argv.slice(1);
7698
8272
  if (rest.some((a) => a.startsWith("--type=") || !a.startsWith("-") || /\.(c|m)?js$/i.test(a))) continue;
7699
8273
  return { pid: p.pid, exe: first };
@@ -7709,7 +8283,7 @@ function winBrandMains(procs, exe) {
7709
8283
  return out;
7710
8284
  }
7711
8285
  function isWinAppExe(p, exe) {
7712
- return typeof p === "string" && /^[a-z]:[\\/]/i.test(p) && win325.basename(p).toLowerCase() === exe.toLowerCase();
8286
+ return typeof p === "string" && /^[a-z]:[\\/]/i.test(p) && win327.basename(p).toLowerCase() === exe.toLowerCase();
7713
8287
  }
7714
8288
  var WIN_SESSION_PS = `$me = (Get-Process -Id $PID).SessionId; $ex = @(Get-Process -Name explorer -ErrorAction SilentlyContinue | ForEach-Object { $_.SessionId }) -join ','; Write-Output ("{0}|{1}" -f $me, $ex)`;
7715
8289
  function parseWinSession(out) {
@@ -7740,11 +8314,11 @@ function parseLsappBundle(out) {
7740
8314
  return m ? m[1] : null;
7741
8315
  }
7742
8316
  function toolBusy(tool, now = Date.now(), dir2 = stateDir(), alive = isAlive) {
7743
- if (!existsSync19(dir2)) return false;
7744
- for (const f of readdirSync11(dir2)) {
8317
+ if (!existsSync20(dir2)) return false;
8318
+ for (const f of readdirSync12(dir2)) {
7745
8319
  if (!f.endsWith(".json")) continue;
7746
8320
  try {
7747
- const s = JSON.parse(readFileSync23(join23(dir2, f), "utf8"));
8321
+ const s = JSON.parse(readFileSync25(join24(dir2, f), "utf8"));
7748
8322
  if (s.tool !== tool || s.state !== "watch" && s.state !== "ask_approval") continue;
7749
8323
  if (typeof s.pid === "number" && s.pid > 0) {
7750
8324
  if (alive(s.pid, s.pidName)) return true;
@@ -7757,7 +8331,7 @@ function toolBusy(tool, now = Date.now(), dir2 = stateDir(), alive = isAlive) {
7757
8331
  return false;
7758
8332
  }
7759
8333
  function defaultThrottleFile() {
7760
- return join23(deskbuddyHome(), "qoderapp", "keeper.json");
8334
+ return join24(deskbuddyHome(), "qoderapp", "keeper.json");
7761
8335
  }
7762
8336
  var QoderCdpKeeper = class {
7763
8337
  #d;
@@ -7847,7 +8421,7 @@ var QoderCdpKeeper = class {
7847
8421
  /** 节流状态(按品牌一份,落盘;读不出当没有) */
7848
8422
  #loadThrottle() {
7849
8423
  try {
7850
- const all = JSON.parse(readFileSync23(this.#file(), "utf8"));
8424
+ const all = JSON.parse(readFileSync25(this.#file(), "utf8"));
7851
8425
  const t = all?.[this.#d.brand.tool] ?? {};
7852
8426
  const out = { lastLaunch: Number(t.lastLaunch) || 0, lastRestart: Number(t.lastRestart) || 0, day: typeof t.day === "string" ? t.day : "", restarts: Number(t.restarts) || 0 };
7853
8427
  if (this.#d.brand.win && isWinAppExe(t.appExe, this.#d.brand.win.exe)) out.appExe = t.appExe;
@@ -7861,14 +8435,14 @@ var QoderCdpKeeper = class {
7861
8435
  try {
7862
8436
  let all = {};
7863
8437
  try {
7864
- all = JSON.parse(readFileSync23(f, "utf8"));
8438
+ all = JSON.parse(readFileSync25(f, "utf8"));
7865
8439
  } catch {
7866
8440
  all = {};
7867
8441
  }
7868
8442
  all[this.#d.brand.tool] = t;
7869
- mkdirSync21(join23(f, ".."), { recursive: true });
7870
- writeFileSync21(f + ".tmp", JSON.stringify(all));
7871
- renameSync15(f + ".tmp", f);
8443
+ mkdirSync22(join24(f, ".."), { recursive: true });
8444
+ writeFileSync22(f + ".tmp", JSON.stringify(all));
8445
+ renameSync16(f + ".tmp", f);
7872
8446
  } catch {
7873
8447
  }
7874
8448
  }
@@ -7941,14 +8515,15 @@ var QoderCdpKeeper = class {
7941
8515
  if (this.#d.appPath) return this.#d.appPath();
7942
8516
  if (this.#isWin()) {
7943
8517
  const win = this.#d.brand.win;
7944
- const exists = this.#d.exists ?? existsSync19;
8518
+ const exists = this.#d.exists ?? existsSync20;
7945
8519
  const t = this.#loadThrottle();
7946
- for (const p of [t.appExe, ...win.installPaths?.() ?? []]) if (isWinAppExe(p, win.exe) && exists(p)) return p;
8520
+ const located = this.#d.locatedExe ? this.#d.locatedExe() : cachedWinAppExe(this.#d.brand.tool);
8521
+ for (const p of [t.appExe, located, ...win.installPaths?.() ?? []]) if (isWinAppExe(p, win.exe) && exists(p)) return p;
7947
8522
  return null;
7948
8523
  }
7949
8524
  if (this.#platform() !== "darwin") return null;
7950
8525
  const name = this.#d.brand.macApp.split("/").pop() ?? "";
7951
- for (const p of [this.#d.brand.macApp, join23(homedir7(), "Applications", name)]) if (existsSync19(p)) return p;
8526
+ for (const p of [this.#d.brand.macApp, join24(homedir7(), "Applications", name)]) if (existsSync20(p)) return p;
7952
8527
  return null;
7953
8528
  }
7954
8529
  /** 一轮:健康检查 → 活着核对属主。🔴 平时不重启 App;只有端口无响应满 HUNG_RESTART_MS 时按节流与在场规则重启 */
@@ -8215,33 +8790,33 @@ var DUMATE_BRAND = {
8215
8790
  installPaths: () => {
8216
8791
  const out = [];
8217
8792
  const la = process.env.LOCALAPPDATA;
8218
- if (la) out.push(join24(la, "Programs", "DuMate", "DuMate.exe"), join24(la, "Programs", "qianfan-desktop-app", "DuMate.exe"));
8219
- for (const pf of [process.env.ProgramFiles, process.env["ProgramFiles(x86)"]]) if (pf) out.push(join24(pf, "DuMate", "DuMate.exe"));
8793
+ if (la) out.push(join25(la, "Programs", "DuMate", "DuMate.exe"), join25(la, "Programs", "qianfan-desktop-app", "DuMate.exe"));
8794
+ for (const pf of [process.env.ProgramFiles, process.env["ProgramFiles(x86)"]]) if (pf) out.push(join25(pf, "DuMate", "DuMate.exe"));
8220
8795
  return out;
8221
8796
  }
8222
8797
  }
8223
8798
  };
8224
8799
  var DM_DOWN_ROUNDS = 2;
8225
8800
  var DM_ACTIVE_MS = 10 * 60 * 1e3;
8226
- function dumateActivity(now = Date.now(), dataHome = dumateDataHome(), sessionsRoot = join24(qianfanHome(), "workspace", "sessions")) {
8801
+ function dumateActivity(now = Date.now(), dataHome = dumateDataHome(), sessionsRoot = join25(qianfanHome(), "workspace", "sessions")) {
8227
8802
  let latest = -1;
8228
- const xdg = join24(dataHome, "qianfan_desk_xdg");
8803
+ const xdg = join25(dataHome, "qianfan_desk_xdg");
8229
8804
  let users = [];
8230
8805
  try {
8231
- users = readdirSync12(xdg);
8806
+ users = readdirSync13(xdg);
8232
8807
  } catch {
8233
8808
  users = [];
8234
8809
  }
8235
8810
  let found = false;
8236
8811
  for (const u of users) {
8237
- const dir2 = join24(xdg, u, "data", "opencode");
8812
+ const dir2 = join25(xdg, u, "data", "opencode");
8238
8813
  try {
8239
- latest = Math.max(latest, statSync10(join24(dir2, "opencode.db")).mtimeMs);
8814
+ latest = Math.max(latest, statSync11(join25(dir2, "opencode.db")).mtimeMs);
8240
8815
  found = true;
8241
8816
  } catch {
8242
8817
  }
8243
8818
  try {
8244
- const w = statSync10(join24(dir2, "opencode.db-wal"));
8819
+ const w = statSync11(join25(dir2, "opencode.db-wal"));
8245
8820
  if (w.size > 0) latest = Math.max(latest, w.mtimeMs);
8246
8821
  } catch {
8247
8822
  }
@@ -8252,7 +8827,7 @@ function dumateActivity(now = Date.now(), dataHome = dumateDataHome(), sessionsR
8252
8827
  return "idle";
8253
8828
  }
8254
8829
  function dmKeeperThrottleFile() {
8255
- return join24(deskbuddyHome(), "dmapp", "keeper.json");
8830
+ return join25(deskbuddyHome(), "dmapp", "keeper.json");
8256
8831
  }
8257
8832
  var DumateKeeper = class {
8258
8833
  #d;
@@ -8362,7 +8937,8 @@ var DUMATE_SHORTCUT_APP = {
8362
8937
  port: dmPort,
8363
8938
  wanted: shortcutsWanted,
8364
8939
  off: () => loadConfig().dumateShortcuts === false,
8365
- skipEnv: "DESKBUDDY_SKIP_DM_LNK"
8940
+ skipEnv: "DESKBUDDY_SKIP_DM_LNK",
8941
+ locatedExe: () => cachedWinAppExe("dumate")
8366
8942
  };
8367
8943
  function ensureDumateShortcuts(d, ps = defaultPs) {
8368
8944
  return ensureAppShortcuts(DUMATE_SHORTCUT_APP, d, ps);
@@ -8433,7 +9009,8 @@ function qoderShortcutApp(tool) {
8433
9009
  port: () => portOf2(tool),
8434
9010
  wanted: () => qoderShortcutsWanted(tool),
8435
9011
  off: () => loadConfig().qoderShortcuts === false,
8436
- skipEnv: "DESKBUDDY_SKIP_QODER_LNK"
9012
+ skipEnv: "DESKBUDDY_SKIP_QODER_LNK",
9013
+ locatedExe: () => cachedWinAppExe(tool)
8437
9014
  };
8438
9015
  }
8439
9016
  function ensureQoderShortcuts(tool, d, ps = defaultPs) {
@@ -8447,15 +9024,15 @@ function restoreQoderShortcutsSync(tool, d, ps = defaultPsSync) {
8447
9024
  }
8448
9025
 
8449
9026
  // src/adapters/traeHook.ts
8450
- import { chmodSync as chmodSync5, existsSync as existsSync20, mkdirSync as mkdirSync22, readFileSync as readFileSync24, realpathSync as realpathSync5, renameSync as renameSync16, rmSync as rmSync13, statSync as statSync11, writeFileSync as writeFileSync22 } from "fs";
8451
- import { dirname as dirname13, join as join26 } from "path";
9027
+ import { chmodSync as chmodSync5, existsSync as existsSync21, mkdirSync as mkdirSync23, readFileSync as readFileSync26, realpathSync as realpathSync5, renameSync as renameSync17, rmSync as rmSync13, statSync as statSync12, writeFileSync as writeFileSync23 } from "fs";
9028
+ import { dirname as dirname13, join as join27 } from "path";
8452
9029
  var TRAE_EVENTS = ["UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop", "Notification"];
8453
9030
  var TRAE_NOTICE_TYPES = ["permission_prompt", "ask_user_question", "document_review", "browser_interaction"];
8454
9031
  var TRAE_NOTICE_MATCHER = `^(${TRAE_NOTICE_TYPES.join("|")})$`;
8455
9032
  var TRAE_NOTICE_TIMEOUT_S = 7 * 24 * 60 * 60;
8456
9033
  function readHooksFile(path) {
8457
- if (!existsSync20(path)) return {};
8458
- const raw = readFileSync24(path, "utf8");
9034
+ if (!existsSync21(path)) return {};
9035
+ const raw = readFileSync26(path, "utf8");
8459
9036
  if (raw.trim() === "") return {};
8460
9037
  let parsed;
8461
9038
  try {
@@ -8477,19 +9054,19 @@ function writeTarget(path) {
8477
9054
  }
8478
9055
  }
8479
9056
  function writeHooksFile(linkPath, f) {
8480
- mkdirSync22(dirname13(linkPath), { recursive: true });
9057
+ mkdirSync23(dirname13(linkPath), { recursive: true });
8481
9058
  const path = writeTarget(linkPath);
8482
9059
  let mode;
8483
9060
  try {
8484
- mode = statSync11(path).mode & 511;
9061
+ mode = statSync12(path).mode & 511;
8485
9062
  } catch {
8486
9063
  mode = void 0;
8487
9064
  }
8488
9065
  const tmp = path + ".deskbuddy.tmp";
8489
9066
  try {
8490
- writeFileSync22(tmp, JSON.stringify(f, null, 2) + "\n", "utf8");
9067
+ writeFileSync23(tmp, JSON.stringify(f, null, 2) + "\n", "utf8");
8491
9068
  if (mode !== void 0) chmodSync5(tmp, mode);
8492
- renameSync16(tmp, path);
9069
+ renameSync17(tmp, path);
8493
9070
  } catch (e) {
8494
9071
  try {
8495
9072
  rmSync13(tmp, { force: true });
@@ -8536,7 +9113,7 @@ function strip(f, scripts) {
8536
9113
  }
8537
9114
  function installTraeHook(path, hookPath, events = TRAE_EVENTS, platform = process.platform) {
8538
9115
  const f = readHooksFile(path);
8539
- const gatePath = join26(dirname13(hookPath), "gate.cjs");
9116
+ const gatePath = join27(dirname13(hookPath), "gate.cjs");
8540
9117
  const before = JSON.stringify(f);
8541
9118
  strip(f, [hookPath, gatePath]);
8542
9119
  if (f.version === void 0) f.version = 1;
@@ -8553,13 +9130,13 @@ function installTraeHook(path, hookPath, events = TRAE_EVENTS, platform = proces
8553
9130
  if (JSON.stringify(f) !== before) writeHooksFile(path, f);
8554
9131
  }
8555
9132
  function uninstallTraeHook(path, hookPath) {
8556
- if (!existsSync20(path)) return;
9133
+ if (!existsSync21(path)) return;
8557
9134
  const f = readHooksFile(path);
8558
- if (strip(f, [hookPath, join26(dirname13(hookPath), "gate.cjs")])) writeHooksFile(path, f);
9135
+ if (strip(f, [hookPath, join27(dirname13(hookPath), "gate.cjs")])) writeHooksFile(path, f);
8559
9136
  }
8560
9137
  function isTraeHookInstalled(path, hookPath) {
8561
9138
  try {
8562
- if (!existsSync20(path)) return false;
9139
+ if (!existsSync21(path)) return false;
8563
9140
  const f = readHooksFile(path);
8564
9141
  return Object.values(f.hooks ?? {}).some((groups) => (groups ?? []).some((g) => (g?.hooks ?? []).some((h) => mentions2(h?.command, hookPath))));
8565
9142
  } catch {
@@ -8569,19 +9146,19 @@ function isTraeHookInstalled(path, hookPath) {
8569
9146
  var TRAE_NOTICE = " TRAE\uFF1A\u8BF7\u5728 TRAE \u7684 \u8BBE\u7F6E > Hooks \u91CC\u786E\u8BA4\u300C\u5168\u5C40\u300DHooks \u5DF2\u542F\u7528\u3001\u8FD0\u884C\u65B9\u5F0F\u9009\u300C\u672C\u5730\u81EA\u52A8\u8FD0\u884C\u300D\uFF08\u6C99\u7BB1\u91CC hook \u8FDE\u4E0D\u4E0A\u5B88\u62A4\u8FDB\u7A0B\uFF09\uFF1B\u7535\u8111\u4E0A\u5F39\u6388\u6743\u6846\u65F6\u8BBE\u5907\u4E0A\u4F1A\u51FA\u73B0\u63D0\u793A\u5361\uFF0C\u9700\u56DE\u5230\u7535\u8111\u4E0A\u5904\u7406";
8570
9147
 
8571
9148
  // src/commands/tools.ts
8572
- import { existsSync as existsSync22, readFileSync as readFileSync26 } from "fs";
9149
+ import { existsSync as existsSync23, readFileSync as readFileSync28 } from "fs";
8573
9150
 
8574
9151
  // src/adapters/cbChannel.ts
8575
- import { chmodSync as chmodSync6, existsSync as existsSync21, lstatSync as lstatSync2, mkdirSync as mkdirSync23, readFileSync as readFileSync25, realpathSync as realpathSync6, renameSync as renameSync17, statSync as statSync12, writeFileSync as writeFileSync23 } from "fs";
8576
- import { dirname as dirname14, join as join27 } from "path";
9152
+ import { chmodSync as chmodSync6, existsSync as existsSync22, lstatSync as lstatSync2, mkdirSync as mkdirSync24, readFileSync as readFileSync27, realpathSync as realpathSync6, renameSync as renameSync18, statSync as statSync13, writeFileSync as writeFileSync24 } from "fs";
9153
+ import { dirname as dirname14, join as join28 } from "path";
8577
9154
  var CB_MCP_NAME = "deskbuddy";
8578
9155
  var CB_CHANNEL_SCRIPT = "cbchannel.cjs";
8579
9156
  function cbChannelScript(hookPath) {
8580
- return join27(dirname14(hookPath), CB_CHANNEL_SCRIPT);
9157
+ return join28(dirname14(hookPath), CB_CHANNEL_SCRIPT);
8581
9158
  }
8582
9159
  function read(path) {
8583
- if (!existsSync21(path)) return {};
8584
- const raw = readFileSync25(path, "utf8");
9160
+ if (!existsSync22(path)) return {};
9161
+ const raw = readFileSync27(path, "utf8");
8585
9162
  if (raw.trim() === "") return {};
8586
9163
  let v;
8587
9164
  try {
@@ -8599,17 +9176,17 @@ function write2(file3, v) {
8599
9176
  } catch {
8600
9177
  path = file3;
8601
9178
  }
8602
- mkdirSync23(dirname14(path), { recursive: true });
9179
+ mkdirSync24(dirname14(path), { recursive: true });
8603
9180
  let mode;
8604
9181
  try {
8605
- mode = statSync12(path).mode & 511;
9182
+ mode = statSync13(path).mode & 511;
8606
9183
  } catch {
8607
9184
  mode = void 0;
8608
9185
  }
8609
9186
  const tmp = path + ".deskbuddy.tmp";
8610
- writeFileSync23(tmp, JSON.stringify(v, null, 2), "utf8");
9187
+ writeFileSync24(tmp, JSON.stringify(v, null, 2), "utf8");
8611
9188
  if (mode !== void 0) chmodSync6(tmp, mode);
8612
- renameSync17(tmp, path);
9189
+ renameSync18(tmp, path);
8613
9190
  }
8614
9191
  function ours(entry) {
8615
9192
  if (!entry || typeof entry !== "object") return false;
@@ -8628,7 +9205,7 @@ function installCbChannel(mcpPath, scriptPath, nodePath = CB_NODE) {
8628
9205
  write2(mcpPath, f);
8629
9206
  }
8630
9207
  function uninstallCbChannel(mcpPath) {
8631
- if (!existsSync21(mcpPath)) return;
9208
+ if (!existsSync22(mcpPath)) return;
8632
9209
  const f = read(mcpPath);
8633
9210
  const servers = f.mcpServers;
8634
9211
  if (!servers || typeof servers !== "object" || Array.isArray(servers) || !ours(servers[CB_MCP_NAME])) return;
@@ -8649,8 +9226,8 @@ function codebuddyWanted(d = { standalone: () => standaloneProbe() }) {
8649
9226
  }
8650
9227
  function fileContains(path, needle) {
8651
9228
  try {
8652
- if (!existsSync22(path)) return false;
8653
- return readFileSync26(path, "utf8").includes(needle);
9229
+ if (!existsSync23(path)) return false;
9230
+ return readFileSync28(path, "utf8").includes(needle);
8654
9231
  } catch {
8655
9232
  return false;
8656
9233
  }
@@ -8697,6 +9274,7 @@ var ADAPTERS = {
8697
9274
  if (tool === "workbuddy") return installCodebuddyTerminalApprovals(gate);
8698
9275
  if (!WINDOW_GATE_TOOLS.has(tool)) return false;
8699
9276
  installCcWindowGate(p, gate, tool);
9277
+ if (tool === "qoder_cn" || tool === "qoder") installCcPreToolQuestion(p, gate, tool);
8700
9278
  return true;
8701
9279
  },
8702
9280
  // asyncRewake 只有 Claude Code 认(2.1.280 实测);别家当成同步 hook 会一直等
@@ -8751,7 +9329,7 @@ ADAPTERS["dumate-http"] = {
8751
9329
  uninstall: () => restoreDumateShortcutsSync({ log: dmLnkLog }),
8752
9330
  isInstalled: (p) => {
8753
9331
  try {
8754
- const enabled = JSON.parse(readFileSync26(p, "utf8")).enabledTools;
9332
+ const enabled = JSON.parse(readFileSync28(p, "utf8")).enabledTools;
8755
9333
  return Array.isArray(enabled) && enabled.includes("dumate");
8756
9334
  } catch {
8757
9335
  return false;
@@ -8772,7 +9350,7 @@ ADAPTERS["trae-hooks"] = {
8772
9350
  extraConfigPaths: (tool, purpose) => {
8773
9351
  if (tool !== "trae") return [];
8774
9352
  const primary = pathResolve(traeHooksPath());
8775
- return traeHooksPaths().filter((p) => pathResolve(p) !== primary && (purpose === "uninstall" || existsSync22(pathDirname(p))));
9353
+ return traeHooksPaths().filter((p) => pathResolve(p) !== primary && (purpose === "uninstall" || existsSync23(pathDirname(p))));
8776
9354
  }
8777
9355
  };
8778
9356
  var COZE_NOTICE = " \u6263\u5B50\u4E0D\u9700\u8981\u6539\u5B83\u7684\u4EFB\u4F55\u914D\u7F6E\uFF1A\u5B88\u62A4\u8FDB\u7A0B\u5728\u8DD1\u65F6\u4F1A\u8BA9\u6263\u5B50\u5E26\u8C03\u8BD5\u7AEF\u53E3\u542F\u52A8\uFF08\u4F60\u4E0D\u5728\u7535\u8111\u524D\u4E14\u6263\u5B50\u7A7A\u95F2\u65F6\u624D\u4F1A\u91CD\u5F00\u5B83\uFF09\uFF0C\u72B6\u6001\u548C\u5F85\u786E\u8BA4\u90FD\u4F1A\u63A8\u5230\u8BBE\u5907";
@@ -8784,7 +9362,7 @@ ADAPTERS["coze-cdp"] = {
8784
9362
  },
8785
9363
  isInstalled: (p) => {
8786
9364
  try {
8787
- const enabled = JSON.parse(readFileSync26(p, "utf8")).enabledTools;
9365
+ const enabled = JSON.parse(readFileSync28(p, "utf8")).enabledTools;
8788
9366
  return Array.isArray(enabled) && enabled.includes("coze");
8789
9367
  } catch {
8790
9368
  return false;
@@ -8815,9 +9393,9 @@ function subscribedEvents(registry, tool, fallback) {
8815
9393
  }
8816
9394
 
8817
9395
  // src/wrap/pty.ts
8818
- import { chmodSync as chmodSync7, existsSync as existsSync23, statSync as statSync13 } from "fs";
9396
+ import { chmodSync as chmodSync7, existsSync as existsSync24, statSync as statSync14 } from "fs";
8819
9397
  import { createRequire as createRequire4 } from "module";
8820
- import { dirname as dirname15, join as join28 } from "path";
9398
+ import { dirname as dirname15, join as join29 } from "path";
8821
9399
  import { fileURLToPath as fileURLToPath4 } from "url";
8822
9400
  var req = createRequire4(import.meta.url);
8823
9401
  var VENDORED_PTY_PLATFORMS = ["darwin-arm64", "darwin-x64", "win32-arm64", "win32-x64"];
@@ -8827,8 +9405,8 @@ function hasVendoredPty(platform = process.platform, arch = process.arch) {
8827
9405
  function vendoredPtyDir(from = dirname15(fileURLToPath4(import.meta.url))) {
8828
9406
  let dir2 = from;
8829
9407
  for (let i = 0; i < 4; i++) {
8830
- const cand = join28(dir2, "vendor", "node-pty");
8831
- if (existsSync23(join28(cand, "package.json"))) return cand;
9408
+ const cand = join29(dir2, "vendor", "node-pty");
9409
+ if (existsSync24(join29(cand, "package.json"))) return cand;
8832
9410
  const up = dirname15(dir2);
8833
9411
  if (up === dir2) break;
8834
9412
  dir2 = up;
@@ -8838,8 +9416,8 @@ function vendoredPtyDir(from = dirname15(fileURLToPath4(import.meta.url))) {
8838
9416
  function fixSpawnHelper(ptyDir) {
8839
9417
  if (process.platform !== "darwin") return;
8840
9418
  try {
8841
- const helper = join28(ptyDir, "prebuilds", `darwin-${process.arch}`, "spawn-helper");
8842
- if (existsSync23(helper) && (statSync13(helper).mode & 73) === 0) chmodSync7(helper, 493);
9419
+ const helper = join29(ptyDir, "prebuilds", `darwin-${process.arch}`, "spawn-helper");
9420
+ if (existsSync24(helper) && (statSync14(helper).mode & 73) === 0) chmodSync7(helper, 493);
8843
9421
  } catch {
8844
9422
  }
8845
9423
  }
@@ -8943,10 +9521,10 @@ function accentLine(term, tabRow = /←.*[☐☒].*Submit.*→/) {
8943
9521
  }
8944
9522
 
8945
9523
  // src/commands/migrateQoder.ts
8946
- import { dirname as dirname17, join as join29 } from "path";
9524
+ import { dirname as dirname17, join as join30 } from "path";
8947
9525
 
8948
9526
  // src/core/detect.ts
8949
- import { existsSync as existsSync24 } from "fs";
9527
+ import { existsSync as existsSync25 } from "fs";
8950
9528
  import { dirname as dirname16 } from "path";
8951
9529
 
8952
9530
  // src/daemon/cozeApp/brand.ts
@@ -9026,7 +9604,7 @@ function toolDetectDirs(tool) {
9026
9604
  }
9027
9605
  var defaultDetectDeps = () => ({
9028
9606
  located: (tool) => locate(tool) != null,
9029
- exists: existsSync24,
9607
+ exists: existsSync25,
9030
9608
  env: process.env
9031
9609
  });
9032
9610
  function callerTool(env) {
@@ -9047,7 +9625,7 @@ function migrateLegacyQoder(out, detect = () => detectTools()) {
9047
9625
  try {
9048
9626
  const cfg = loadConfig();
9049
9627
  const hookPath = installedHookPath();
9050
- const scripts = [hookPath, join29(dirname17(hookPath), "gate.cjs")];
9628
+ const scripts = [hookPath, join30(dirname17(hookPath), "gate.cjs")];
9051
9629
  if (!retagCcHook(qoderCnSettingsPath(), scripts, "qoder", "qoder_cn")) return false;
9052
9630
  const hadQoder = cfg.enabledTools.includes("qoder");
9053
9631
  let intl = false;
@@ -9076,26 +9654,26 @@ function migrateLegacyQoder(out, detect = () => detectTools()) {
9076
9654
 
9077
9655
  // src/daemon/loginGuide.ts
9078
9656
  import { execFile as execFile9, execFileSync as execFileSync13 } from "child_process";
9079
- import { chmodSync as chmodSync9, existsSync as existsSync25, mkdirSync as mkdirSync25, readFileSync as readFileSync27, writeFileSync as writeFileSync25 } from "fs";
9657
+ import { chmodSync as chmodSync9, existsSync as existsSync26, mkdirSync as mkdirSync26, readFileSync as readFileSync29, writeFileSync as writeFileSync26 } from "fs";
9080
9658
  import { homedir as homedir9 } from "os";
9081
- import { join as join33 } from "path";
9659
+ import { join as join34 } from "path";
9082
9660
 
9083
9661
  // src/daemon/executor.ts
9084
9662
  import { execFile as execFile7, execFileSync as execFileSync11 } from "child_process";
9085
9663
  import { homedir as homedir8 } from "os";
9086
- import { join as join31 } from "path";
9664
+ import { join as join32 } from "path";
9087
9665
 
9088
9666
  // src/core/codebuddyDirs.ts
9089
- import { join as join30, win32 as win326, posix as posix3 } from "path";
9667
+ import { join as join31, win32 as win328, posix as posix3 } from "path";
9090
9668
  function same(a, b, platform) {
9091
- const pa = platform === "win32" ? win326.resolve(a).toLowerCase() : posix3.resolve(a);
9092
- const pb = platform === "win32" ? win326.resolve(b).toLowerCase() : posix3.resolve(b);
9669
+ const pa = platform === "win32" ? win328.resolve(a).toLowerCase() : posix3.resolve(a);
9670
+ const pb = platform === "win32" ? win328.resolve(b).toLowerCase() : posix3.resolve(b);
9093
9671
  return pa === pb;
9094
9672
  }
9095
9673
  function workbuddyRunConfigDir(stateDir2, home, platform = process.platform) {
9096
- const wb = join30(home, ".workbuddy");
9674
+ const wb = join31(home, ".workbuddy");
9097
9675
  if (typeof stateDir2 !== "string" || !stateDir2) return wb;
9098
- const cb = join30(home, ".codebuddy");
9676
+ const cb = join31(home, ".codebuddy");
9099
9677
  if (same(stateDir2, cb, platform)) return cb;
9100
9678
  return wb;
9101
9679
  }
@@ -9187,7 +9765,7 @@ function buildSpawnPlan(cmd, cwd, gate, home = homedir8(), configDir, platform =
9187
9765
  file: file3,
9188
9766
  args: workbuddyPlan(cmd, gate),
9189
9767
  cwd,
9190
- env: { CODEBUDDY_CONFIG_DIR: cmd.sessionId ? workbuddyRunConfigDir(configDir, home, platform) : join31(home, ".workbuddy") }
9768
+ env: { CODEBUDDY_CONFIG_DIR: cmd.sessionId ? workbuddyRunConfigDir(configDir, home, platform) : join32(home, ".workbuddy") }
9191
9769
  };
9192
9770
  case "qoder_cn":
9193
9771
  case "qoder":
@@ -9455,9 +10033,9 @@ function killTree(child) {
9455
10033
 
9456
10034
  // src/daemon/openWindow.ts
9457
10035
  import { execFile as execFile8, execFileSync as execFileSync12, spawn as spawn4 } from "child_process";
9458
- import { chmodSync as chmodSync8, mkdirSync as mkdirSync24, rmSync as rmSync14, writeFileSync as writeFileSync24 } from "fs";
10036
+ import { chmodSync as chmodSync8, mkdirSync as mkdirSync25, rmSync as rmSync14, writeFileSync as writeFileSync25 } from "fs";
9459
10037
  import { userInfo } from "os";
9460
- import { join as join32 } from "path";
10038
+ import { join as join33 } from "path";
9461
10039
  function safePath(p) {
9462
10040
  return /^(?:[A-Za-z]:[\\/]|\/)/.test(p) && !/[\r\n\0"'`$%;\u2018-\u201b]/.test(p);
9463
10041
  }
@@ -9556,9 +10134,9 @@ var defaultOpenerDeps = (findWt2) => ({
9556
10134
  async function openTerminal(t, d) {
9557
10135
  checkTarget(t);
9558
10136
  if (d.platform === "darwin") {
9559
- mkdirSync24(launchDir(t.home), { recursive: true, mode: 448 });
9560
- const file3 = join32(launchDir(t.home), `${t.runId}.command`);
9561
- writeFileSync24(file3, macCommandScript(t), { encoding: "utf8", mode: 448 });
10137
+ mkdirSync25(launchDir(t.home), { recursive: true, mode: 448 });
10138
+ const file3 = join33(launchDir(t.home), `${t.runId}.command`);
10139
+ writeFileSync25(file3, macCommandScript(t), { encoding: "utf8", mode: 448 });
9562
10140
  chmodSync8(file3, 448);
9563
10141
  await d.run("open", ["-a", "Terminal", file3]);
9564
10142
  return "terminal";
@@ -9577,7 +10155,7 @@ async function openTerminal(t, d) {
9577
10155
  function removeCommandFile(home, runId) {
9578
10156
  if (!RUN_ID_RE.test(runId)) return;
9579
10157
  try {
9580
- rmSync14(join32(launchDir(home), `${runId}.command`), { force: true });
10158
+ rmSync14(join33(launchDir(home), `${runId}.command`), { force: true });
9581
10159
  } catch {
9582
10160
  }
9583
10161
  }
@@ -9659,8 +10237,8 @@ var WORKBUDDY_PROBE_ARGS = ["-p", PROBE_TEXT, "--output-format", "json", "--no-s
9659
10237
  var WORKBUDDY_AUTH_FILE = "Tencent-Cloud.coding-copilot.info";
9660
10238
  function workbuddyAuthFile(home, platform, localAppData = "") {
9661
10239
  const tail = ["CodeBuddyExtension", "Data", "Public", "auth", WORKBUDDY_AUTH_FILE];
9662
- if (platform === "darwin") return join33(home, "Library", "Application Support", ...tail);
9663
- if (platform === "win32") return localAppData ? join33(localAppData, ...tail) : null;
10240
+ if (platform === "darwin") return join34(home, "Library", "Application Support", ...tail);
10241
+ if (platform === "win32") return localAppData ? join34(localAppData, ...tail) : null;
9664
10242
  return null;
9665
10243
  }
9666
10244
  var WORKBUDDY_VIA_APP_NOTE = "WorkBuddy \u5C06\u901A\u8FC7\u684C\u9762 App \u8FD0\u884C\uFF0C\u5728 App \u91CC\u767B\u5F55\u5373\u53EF";
@@ -9674,7 +10252,7 @@ async function checkWorkbuddy(d) {
9674
10252
  const auth = d.workbuddyAuthFile?.() ?? null;
9675
10253
  if (auth && d.exists?.(auth)) return { tool, state: "logged_in", note: "\u72EC\u7ACB\u7248\u51ED\u636E\u6587\u4EF6\u5728" };
9676
10254
  }
9677
- const env = envFor(d, loc, { CODEBUDDY_CONFIG_DIR: join33(d.home, ".workbuddy"), DESKBUDDY_NO_WAKE: "1", [PROBE_ENV]: "1" });
10255
+ const env = envFor(d, loc, { CODEBUDDY_CONFIG_DIR: join34(d.home, ".workbuddy"), DESKBUDDY_NO_WAKE: "1", [PROBE_ENV]: "1" });
9678
10256
  const r = await d.run(loc.file, [...loc.prefixArgs, ...WORKBUDDY_PROBE_ARGS], env, WORKBUDDY_PROBE_TIMEOUT_MS);
9679
10257
  const bad = failedRun(r);
9680
10258
  if (bad) return { tool, state: "unknown", note: bad };
@@ -9759,7 +10337,7 @@ var defaultCheckDeps = () => ({
9759
10337
  home: homedir9(),
9760
10338
  codexConfig: () => {
9761
10339
  try {
9762
- return readFileSync27(join33(codexHome(), "config.toml"), "utf8");
10340
+ return readFileSync29(join34(codexHome(), "config.toml"), "utf8");
9763
10341
  } catch {
9764
10342
  return null;
9765
10343
  }
@@ -9769,9 +10347,9 @@ var defaultCheckDeps = () => ({
9769
10347
  // 与设备任务唤起 App 同一个 locate(locateWorkbuddyAppExe:注册表卸载项 / App Paths / 开始菜单快捷方式…);App 自带 CLI 找得到也算
9770
10348
  workbuddyAppInstalled: () => locateWorkbuddyAppExe() !== null || locateWorkbuddyApp() !== null,
9771
10349
  workbuddyAuthFile: () => workbuddyAuthFile(homedir9(), process.platform, process.env.LOCALAPPDATA ?? ""),
9772
- exists: (p) => existsSync25(p)
10350
+ exists: (p) => existsSync26(p)
9773
10351
  });
9774
- function daemonCheckDeps(base = defaultLocateDeps(), exeHintsAsync = winWorkbuddyExeHintsAsync) {
10352
+ function daemonCheckDeps(base = defaultLocateDeps(), exeHintsAsync = winWorkbuddyLocatorHints) {
9775
10353
  const noPs = { ...base, winAppHints: void 0, winAppExeHints: void 0 };
9776
10354
  return {
9777
10355
  ...defaultCheckDeps(),
@@ -9873,7 +10451,7 @@ function winLoginScript(cmd) {
9873
10451
  return lines.join("\r\n") + "\r\n";
9874
10452
  }
9875
10453
  function loginScriptPath(home, tool, platform) {
9876
- return join33(launchDir(home), `login-${tool}.${platform === "win32" ? "ps1" : "command"}`);
10454
+ return join34(launchDir(home), `login-${tool}.${platform === "win32" ? "ps1" : "command"}`);
9877
10455
  }
9878
10456
  function winLoginWtArgs(script) {
9879
10457
  if (!safePath(script)) throw new Error("\u811A\u672C\u8DEF\u5F84\u91CC\u6709\u4E0D\u80FD\u8FDB\u547D\u4EE4\u884C\u7684\u5B57\u7B26");
@@ -9910,14 +10488,14 @@ var defaultLoginOpenDeps = () => ({
9910
10488
  home: deskbuddyHome(),
9911
10489
  pathDirs: () => childPath().split(":"),
9912
10490
  write: (file3, content) => {
9913
- mkdirSync25(join33(file3, ".."), { recursive: true, mode: 448 });
9914
- writeFileSync25(file3, content, { mode: 448 });
10491
+ mkdirSync26(join34(file3, ".."), { recursive: true, mode: 448 });
10492
+ writeFileSync26(file3, content, { mode: 448 });
9915
10493
  chmodSync9(file3, 448);
9916
10494
  }
9917
10495
  });
9918
10496
  async function installCodebuddyHooks(log) {
9919
10497
  try {
9920
- const { installTools: installTools2 } = await import("./enable-KQYXVSQN.js");
10498
+ const { installTools: installTools2 } = await import("./enable-SIY4AEF7.js");
9921
10499
  installTools2(["workbuddy"], () => {
9922
10500
  }, null, { auto: true });
9923
10501
  } catch (e) {
@@ -10102,8 +10680,8 @@ async function autoEnableLoginLog(tools, check, log) {
10102
10680
 
10103
10681
  // src/daemon/workbuddySetup.ts
10104
10682
  import { execFile as execFile10, spawn as spawn5 } from "child_process";
10105
- import { existsSync as existsSync26 } from "fs";
10106
- import { dirname as dirname18, join as join34 } from "path";
10683
+ import { existsSync as existsSync27 } from "fs";
10684
+ import { dirname as dirname18, join as join35 } from "path";
10107
10685
  var DIALOG_TITLE = "DeskBuddy";
10108
10686
  var BTN_INSTALL = "\u5B89\u88C5";
10109
10687
  var BTN_LATER = "\u6682\u4E0D";
@@ -10242,9 +10820,9 @@ function npmPlan(d) {
10242
10820
  for (const f of whichAllIn("npm", d)) {
10243
10821
  if (!/\.cmd$/i.test(f)) continue;
10244
10822
  const dir2 = dirname18(f);
10245
- const cli = join34(dir2, "node_modules", "npm", "bin", "npm-cli.js");
10823
+ const cli = join35(dir2, "node_modules", "npm", "bin", "npm-cli.js");
10246
10824
  if (!d.exists(cli)) continue;
10247
- const node = join34(dir2, "node.exe");
10825
+ const node = join35(dir2, "node.exe");
10248
10826
  return { file: d.exists(node) ? node : d.nodePath, args: [cli, ...NPM_INSTALL_ARGS] };
10249
10827
  }
10250
10828
  return null;
@@ -10361,7 +10939,7 @@ async function runOffer(source, d, opts = {}) {
10361
10939
  }
10362
10940
  }
10363
10941
  function lockPath() {
10364
- return join34(deskbuddyHome(), "workbuddy-setup.lock");
10942
+ return join35(deskbuddyHome(), "workbuddy-setup.lock");
10365
10943
  }
10366
10944
  var defaultLockDeps = () => ({ path: lockPath(), pid: process.pid, now: () => Date.now(), alive: pidAlive2 });
10367
10945
  var WorkbuddyOffer = class {
@@ -10431,7 +11009,7 @@ function defaultOfferDeps(log) {
10431
11009
  }),
10432
11010
  openLogin: () => defaultOpenLogin(platform),
10433
11011
  installHooks: async () => {
10434
- const { installTools: installTools2 } = await import("./enable-KQYXVSQN.js");
11012
+ const { installTools: installTools2 } = await import("./enable-SIY4AEF7.js");
10435
11013
  installTools2(["workbuddy"], (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`), null, { auto: true });
10436
11014
  },
10437
11015
  log: (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`),
@@ -10520,7 +11098,7 @@ function defaultPromptStartDeps(env = process.env) {
10520
11098
  gui: () => guiAvailable(defaultGuiDeps()),
10521
11099
  spawnChild: (source) => {
10522
11100
  const cli = process.argv[1];
10523
- if (!cli || !existsSync26(cli)) throw new Error("\u627E\u4E0D\u5230 deskbuddy \u81EA\u5DF1\u7684\u5165\u53E3\u6587\u4EF6");
11101
+ if (!cli || !existsSync27(cli)) throw new Error("\u627E\u4E0D\u5230 deskbuddy \u81EA\u5DF1\u7684\u5165\u53E3\u6587\u4EF6");
10524
11102
  const c = spawn5(process.execPath, [cli, "workbuddy-setup", "--from", source], {
10525
11103
  detached: true,
10526
11104
  stdio: "ignore",
@@ -10600,14 +11178,14 @@ function installTools(tools, out, shim = defaultShimEnvForInstall(), opts = {})
10600
11178
  installed.push(tool);
10601
11179
  out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
10602
11180
  try {
10603
- if (adapter.installWindowGate?.(configPath2, join35(dirname19(hookPath), "gate.cjs"), tool)) {
11181
+ if (adapter.installWindowGate?.(configPath2, join36(dirname19(hookPath), "gate.cjs"), tool)) {
10604
11182
  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`);
10605
11183
  }
10606
11184
  } catch (e) {
10607
11185
  out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
10608
11186
  }
10609
11187
  try {
10610
- if (adapter.installRewake?.(configPath2, join35(dirname19(hookPath), "wake.cjs"), tool)) {
11188
+ if (adapter.installRewake?.(configPath2, join36(dirname19(hookPath), "wake.cjs"), tool)) {
10611
11189
  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`);
10612
11190
  }
10613
11191
  } catch (e) {
@@ -10682,6 +11260,7 @@ export {
10682
11260
  codebuddyConfigDir,
10683
11261
  qoderCnSettingsPath,
10684
11262
  qwenworkHome,
11263
+ qwenworkDataHome,
10685
11264
  qoderSettingsPath,
10686
11265
  opencodePluginPath,
10687
11266
  installedHookPath,
@@ -10695,7 +11274,6 @@ export {
10695
11274
  packageVersion,
10696
11275
  installedHookVersion,
10697
11276
  syncHookFile,
10698
- COMMAND_OUTPUT_MAX,
10699
11277
  APP_ID,
10700
11278
  APP_SECRET,
10701
11279
  sign,
@@ -10731,6 +11309,9 @@ export {
10731
11309
  accentLine,
10732
11310
  scrubAgentEnv,
10733
11311
  scrubForCommand,
11312
+ WIN_APP_SPECS,
11313
+ LOCATE_EVERY_MS,
11314
+ winAppLocator,
10734
11315
  defaultLocateDeps,
10735
11316
  PROBE_TIMEOUT_MS,
10736
11317
  PROBE_SHORT_TTL_MS,
@@ -10859,12 +11440,24 @@ export {
10859
11440
  cleanFeedback,
10860
11441
  parseApprovalFrame,
10861
11442
  cleanDeviceName,
11443
+ APP_WAIT_MARGIN_MS,
11444
+ appWaitBudget,
10862
11445
  isExpired,
10863
11446
  NOT_REGISTERED_RE_SRC,
10864
11447
  PROBE,
10865
11448
  APP_IN_USE_REASON,
10866
11449
  QODER_APP_POLL_MS,
10867
11450
  QoderAppTasks,
11451
+ SUMMARY_CAPS,
11452
+ SUMMARY_TOOLS2 as SUMMARY_TOOLS,
11453
+ SUMMARY_NO_OUTPUT,
11454
+ isSummary,
11455
+ withSummaryMark,
11456
+ clipOutput,
11457
+ SUMMARY_WAIT_MS,
11458
+ ptyVerdict,
11459
+ replyVerdict,
11460
+ PtySummaryWatch,
10868
11461
  APP_NOT_RUNNING,
10869
11462
  DumateWatch,
10870
11463
  runHook,