deskbuddy 0.2.0-beta.30 → 0.2.0-beta.31

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";
@@ -1871,12 +1871,12 @@ function isDshPluginInstalled(patchPath) {
1871
1871
  var DSH_NOTICE = " \u5DF2\u5F00\u7740\u7684 DeepSeek Harness\uFF08dsh web / \u684C\u9762\u7AEF\uFF09\u91CD\u542F\u540E\u751F\u6548";
1872
1872
 
1873
1873
  // src/daemon/dumateApp/shortcuts.ts
1874
- import { join as join25 } from "path";
1874
+ import { join as join26 } from "path";
1875
1875
 
1876
1876
  // src/daemon/appShortcuts.ts
1877
1877
  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";
1878
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync12, renameSync as renameSync8, statSync as statSync4, writeFileSync as writeFileSync11 } from "fs";
1879
+ import { join as join10, win32 as win322 } from "path";
1880
1880
 
1881
1881
  // src/core/fileLock.ts
1882
1882
  import { existsSync as existsSync10, mkdirSync as mkdirSync10, readFileSync as readFileSync11, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
@@ -1937,7 +1937,55 @@ function pidAlive2(pid) {
1937
1937
  }
1938
1938
  }
1939
1939
 
1940
+ // src/daemon/winPaths.ts
1941
+ import { win32 } from "path";
1942
+ function isValidWinPath(p) {
1943
+ if (typeof p !== "string" || p.length < 4 || p.length > 1024) return false;
1944
+ if (!/^[a-z]:\\/i.test(p)) return false;
1945
+ if (/[\u0000-\u001f*?"<>|%]/.test(p)) return false;
1946
+ return !p.split("\\").includes("..");
1947
+ }
1948
+ function normalizeHint(raw) {
1949
+ if (typeof raw !== "string") return null;
1950
+ let v = raw.trim();
1951
+ if (!v) return null;
1952
+ if (v.startsWith('"')) {
1953
+ const end = v.indexOf('"', 1);
1954
+ if (end < 0) return null;
1955
+ v = v.slice(1, end);
1956
+ } else {
1957
+ v = v.replace(/,\s*-?\d+$/, "");
1958
+ const m = /^(.*?\.exe)(\s|$)/i.exec(v);
1959
+ if (m) v = m[1];
1960
+ }
1961
+ v = v.replace(/\//g, "\\").replace(/\\+$/, "").trim();
1962
+ return isValidWinPath(v) ? v : null;
1963
+ }
1964
+ function launcherTarget(text, exe) {
1965
+ const kv = /* @__PURE__ */ new Map();
1966
+ for (const line of text.split(/\r?\n/)) {
1967
+ const i = line.indexOf("=");
1968
+ if (i > 0) kv.set(line.slice(0, i).trim(), line.slice(i + 1).trim());
1969
+ }
1970
+ const dir2 = (kv.get("installDir") ?? "").replace(/\//g, "\\").replace(/\\+$/, "");
1971
+ const app = (kv.get("appExecutable") ?? "").replace(/\//g, "\\");
1972
+ const seg = app.split("\\");
1973
+ const shapeOk = seg.length === 1 || seg.length === 3 && seg[0].toLowerCase() === ".qoder-versions" && /^[0-9a-z][0-9a-z._-]*$/i.test(seg[1]);
1974
+ if (!shapeOk || seg[seg.length - 1].toLowerCase() !== exe.toLowerCase() || !isValidWinPath(dir2)) return null;
1975
+ const p = win32.join(dir2, app);
1976
+ return isValidWinPath(p) ? p : null;
1977
+ }
1978
+
1940
1979
  // src/daemon/appShortcuts.ts
1980
+ function exeNameOf(app) {
1981
+ let p = null;
1982
+ try {
1983
+ p = app.locatedExe?.() ?? null;
1984
+ } catch {
1985
+ p = null;
1986
+ }
1987
+ return p ? win322.basename(p) : app.exe;
1988
+ }
1941
1989
  var LNK_LIST_PS = String.raw`[Console]::OutputEncoding=[Text.Encoding]::UTF8
1942
1990
  $s = New-Object -ComObject WScript.Shell
1943
1991
  $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 +1999,16 @@ foreach ($d in $dirs) {
1951
1999
  ConvertTo-Json -InputObject @($out) -Compress`;
1952
2000
  var LNK_WRITE_PS = String.raw`[Console]::OutputEncoding=[Text.Encoding]::UTF8
1953
2001
  $ops = $env:DB_LNK_OPS | ConvertFrom-Json
2002
+ # 允许的 Launcher 目标(完整路径,小写);PowerShell 5.1 的 ConvertFrom-Json 数组要展开
2003
+ $launchers = @($env:DB_LNK_LAUNCHERS | ConvertFrom-Json | ForEach-Object { $_ } | ForEach-Object { ([string]$_).ToLowerInvariant() })
1954
2004
  $s = New-Object -ComObject WScript.Shell
1955
2005
  $out = @()
1956
2006
  foreach ($o in @($ops)) {
1957
2007
  $ok = $false
1958
2008
  try {
1959
2009
  $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 }
2010
+ $t = [string]$l.TargetPath
2011
+ 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
2012
  } catch {}
1962
2013
  $out += [pscustomobject]@{ path = [string]$o.path; ok = $ok }
1963
2014
  }
@@ -1969,7 +2020,47 @@ function hasPortFlag(args) {
1969
2020
  return /(^|\s)--remote-debugging-port(=|\s|$)/.test(args);
1970
2021
  }
1971
2022
  function isAppLnk(l, exe) {
1972
- return win32.basename(l.target || "").toLowerCase() === exe.toLowerCase();
2023
+ if (typeof exe === "function") return exe(l);
2024
+ return win322.basename(l.target || "").toLowerCase() === exe.toLowerCase();
2025
+ }
2026
+ function lnkMatcher(app, readText = readSmall, restore = false) {
2027
+ const exe = exeNameOf(app);
2028
+ let located = null;
2029
+ try {
2030
+ located = app.locatedExe?.() ?? null;
2031
+ } catch {
2032
+ located = null;
2033
+ }
2034
+ const cache2 = /* @__PURE__ */ new Map();
2035
+ return (l) => {
2036
+ const target = l.target || "";
2037
+ if (win322.basename(target).toLowerCase() === exe.toLowerCase()) return true;
2038
+ if (!located && !restore || !isValidWinPath(target) || !/\.exe$/i.test(target)) return false;
2039
+ const k = target.toLowerCase();
2040
+ if (!cache2.has(k)) {
2041
+ const ini = readText(win322.join(win322.dirname(target), "install.ini"));
2042
+ const to = ini ? launcherTarget(ini, exe) : null;
2043
+ cache2.set(k, !!to && (restore || !!located && to.toLowerCase() === located.toLowerCase()));
2044
+ }
2045
+ return cache2.get(k) === true;
2046
+ };
2047
+ }
2048
+ function launcherTargets(list, writes, exe) {
2049
+ const byPath = new Map(list.map((l) => [l.path.toLowerCase(), l.target]));
2050
+ const out = /* @__PURE__ */ new Set();
2051
+ for (const w of writes) {
2052
+ const t = byPath.get(w.path.toLowerCase());
2053
+ if (t && win322.basename(t).toLowerCase() !== exe.toLowerCase()) out.add(t.toLowerCase());
2054
+ }
2055
+ return [...out];
2056
+ }
2057
+ function readSmall(p) {
2058
+ try {
2059
+ if (statSync4(p).size > 64 * 1024) return null;
2060
+ return readFileSync12(p, "utf8");
2061
+ } catch {
2062
+ return null;
2063
+ }
1973
2064
  }
1974
2065
  var keyOf = (p) => p.toLowerCase();
1975
2066
  function planPatchFor(exe, list, records, port) {
@@ -2030,9 +2121,9 @@ function parseJsonArray(out) {
2030
2121
  }
2031
2122
  }
2032
2123
  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 : "" }));
2124
+ const arr2 = parseJsonArray(out);
2125
+ if (!arr2) return null;
2126
+ 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
2127
  }
2037
2128
  var psArgs = (script) => ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script];
2038
2129
  var defaultPs = (script, env) => new Promise((done) => {
@@ -2062,7 +2153,12 @@ var nowOf = (d) => d.now ? d.now() : Date.now();
2062
2153
  var fileOf = (app, d) => d.file ?? recordsFile(app);
2063
2154
  var failFileOf = (app, d) => d.failFile ?? join10(deskbuddyHome(), app.dir, `${app.stem}-fail.json`);
2064
2155
  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 });
2156
+ var writeEnv = (app, ops, list) => ({
2157
+ DB_LNK_OPS: JSON.stringify(ops),
2158
+ DB_LNK_EXE: exeNameOf(app),
2159
+ DB_LNK_LAUNCHERS: JSON.stringify(launcherTargets(list, ops, exeNameOf(app)))
2160
+ });
2161
+ var matcherOf = (app, d, restore = false) => lnkMatcher(app, d.readText, restore);
2066
2162
  function loadFails(app, d) {
2067
2163
  try {
2068
2164
  const v = JSON.parse(readFileSync12(failFileOf(app, d), "utf8"));
@@ -2116,7 +2212,7 @@ function applyResults(app, d, writes, results, records, prev) {
2116
2212
  return out;
2117
2213
  }
2118
2214
  function restoreWith(app, d, list, records, write3) {
2119
- const writes = planRestoreFor(app.exe, list, records, portOf(app, d));
2215
+ const writes = planRestoreFor(matcherOf(app, d, true), list, records, portOf(app, d));
2120
2216
  const finish = (results) => {
2121
2217
  const ok = new Set((results ?? []).filter((r2) => r2 && r2.ok === true && typeof r2.path === "string").map((r2) => keyOf(r2.path)));
2122
2218
  const keep = {};
@@ -2146,7 +2242,7 @@ async function ensureAppShortcuts(app, d, ps = defaultPs) {
2146
2242
  const prev = loadRecords(fileOf(app, d));
2147
2243
  const fails = loadFails(app, d);
2148
2244
  const now = nowOf(d);
2149
- const plan = planPatchFor(app.exe, list, prev, portOf(app, d));
2245
+ const plan = planPatchFor(matcherOf(app, d), list, prev, portOf(app, d));
2150
2246
  const writes = plan.writes.filter((w) => {
2151
2247
  const t = fails[keyOf(w.path)];
2152
2248
  return !t || now - t >= FAIL_RETRY_MS;
@@ -2158,11 +2254,11 @@ async function ensureAppShortcuts(app, d, ps = defaultPs) {
2158
2254
  if (prev[k]) records[k] = prev[k];
2159
2255
  else delete records[k];
2160
2256
  }
2161
- const results = parseJsonArray(await ps(LNK_WRITE_PS, writeEnv(app, writes)));
2257
+ const results = parseJsonArray(await ps(LNK_WRITE_PS, writeEnv(app, writes, list)));
2162
2258
  let next = applyResults(app, d, writes, results, records, prev);
2163
2259
  if (!wanted()) {
2164
2260
  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))));
2261
+ if (list2) next = await restoreWith(app, d, list2, next, async (ops) => parseJsonArray(await ps(LNK_WRITE_PS, writeEnv(app, ops, list2))));
2166
2262
  }
2167
2263
  saveRecords(next, fileOf(app, d));
2168
2264
  } finally {
@@ -2190,9 +2286,9 @@ function ensureAppShortcutsSync(app, d, ps = defaultPsSync) {
2190
2286
  return;
2191
2287
  }
2192
2288
  const prev = loadRecords(fileOf(app, d));
2193
- const { writes, records } = planPatchFor(app.exe, list, prev, portOf(app, d));
2289
+ const { writes, records } = planPatchFor(matcherOf(app, d), list, prev, portOf(app, d));
2194
2290
  if (writes.length === 0) return;
2195
- const results = parseJsonArray(ps(LNK_WRITE_PS, writeEnv(app, writes)));
2291
+ const results = parseJsonArray(ps(LNK_WRITE_PS, writeEnv(app, writes, list)));
2196
2292
  saveRecords(applyResults(app, d, writes, results, records, prev), fileOf(app, d));
2197
2293
  } finally {
2198
2294
  release2();
@@ -2213,7 +2309,7 @@ function restoreAppShortcutsSync(app, d, ps = defaultPsSync) {
2213
2309
  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
2310
  return;
2215
2311
  }
2216
- const keep = restoreWith(app, d, list, loadRecords(fileOf(app, d)), (ops) => parseJsonArray(ps(LNK_WRITE_PS, writeEnv(app, ops))));
2312
+ const keep = restoreWith(app, d, list, loadRecords(fileOf(app, d)), (ops) => parseJsonArray(ps(LNK_WRITE_PS, writeEnv(app, ops, list))));
2217
2313
  saveRecords(keep, fileOf(app, d));
2218
2314
  } finally {
2219
2315
  release2();
@@ -2221,14 +2317,14 @@ function restoreAppShortcutsSync(app, d, ps = defaultPsSync) {
2221
2317
  }
2222
2318
 
2223
2319
  // src/daemon/dumateApp/keeper.ts
2224
- import { readdirSync as readdirSync12, statSync as statSync10 } from "fs";
2225
- import { join as join24 } from "path";
2320
+ import { readdirSync as readdirSync13, statSync as statSync12 } from "fs";
2321
+ import { join as join25 } from "path";
2226
2322
 
2227
2323
  // src/daemon/dumateWatch.ts
2228
2324
  import { execFileSync as execFileSync10, spawn as spawn3 } from "child_process";
2229
- import { readdirSync as readdirSync10, realpathSync as realpathSync4, statSync as statSync9 } from "fs";
2325
+ import { readdirSync as readdirSync11, realpathSync as realpathSync4, statSync as statSync11 } from "fs";
2230
2326
  import { request as httpRequest } from "http";
2231
- import { join as join22 } from "path";
2327
+ import { join as join23 } from "path";
2232
2328
 
2233
2329
  // hook/ocTranslate.mjs
2234
2330
  function toCcTool(tool, args) {
@@ -2943,8 +3039,8 @@ function traeNoticeSummary(toolName, toolInput, described, notice) {
2943
3039
 
2944
3040
  // src/daemon/workbuddyApp/keeper.ts
2945
3041
  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";
3042
+ import { existsSync as existsSync19, mkdirSync as mkdirSync21, readdirSync as readdirSync10, readFileSync as readFileSync23, renameSync as renameSync15, writeFileSync as writeFileSync21 } from "fs";
3043
+ import { delimiter as delimiter3, join as join22, resolve as resolve4 } from "path";
2948
3044
 
2949
3045
  // src/core/process.ts
2950
3046
  import { execFileSync as execFileSync5 } from "child_process";
@@ -3057,7 +3153,7 @@ function programOf(args) {
3057
3153
  }
3058
3154
 
3059
3155
  // 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";
3156
+ import { closeSync, mkdirSync as mkdirSync12, openSync, readdirSync as readdirSync2, readFileSync as readFileSync13, readSync, realpathSync, renameSync as renameSync9, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync12 } from "fs";
3061
3157
  import { join as join11 } from "path";
3062
3158
  var ENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
3063
3159
  var SID_RE = /^[A-Za-z0-9_-]{1,64}$/;
@@ -3079,7 +3175,7 @@ function writeEnded(s, now = Date.now()) {
3079
3175
  for (const n of readdirSync2(dir2)) {
3080
3176
  const f = join11(dir2, n);
3081
3177
  try {
3082
- if (now - statSync4(f).mtimeMs > ENDED_TTL_MS) rmSync5(f, { force: true });
3178
+ if (now - statSync5(f).mtimeMs > ENDED_TTL_MS) rmSync5(f, { force: true });
3083
3179
  } catch {
3084
3180
  }
3085
3181
  }
@@ -3104,7 +3200,7 @@ function projectDirName(cwd) {
3104
3200
  function readChunk(file3, fromEnd, max) {
3105
3201
  let fd;
3106
3202
  try {
3107
- const size = statSync4(file3).size;
3203
+ const size = statSync5(file3).size;
3108
3204
  const len = Math.min(size, max);
3109
3205
  const buf = Buffer.alloc(len);
3110
3206
  fd = openSync(file3, "r");
@@ -3137,7 +3233,7 @@ function claudeTranscriptCwd(sessionId, home = claudeHome()) {
3137
3233
  for (const d of dirs) {
3138
3234
  const file3 = join11(home, "projects", d, `${sessionId}.jsonl`);
3139
3235
  try {
3140
- statSync4(file3);
3236
+ statSync5(file3);
3141
3237
  } catch {
3142
3238
  continue;
3143
3239
  }
@@ -3173,7 +3269,7 @@ function resolveEndedCwd(sessionId, tool, d) {
3173
3269
  }
3174
3270
  function isDirSync(p) {
3175
3271
  try {
3176
- return statSync4(p).isDirectory();
3272
+ return statSync5(p).isDirectory();
3177
3273
  } catch {
3178
3274
  return false;
3179
3275
  }
@@ -3949,9 +4045,9 @@ function shimState(tool, binDir, pathEnv, platform = process.platform) {
3949
4045
 
3950
4046
  // src/daemon/workbuddyAppDb.ts
3951
4047
  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";
4048
+ import { closeSync as closeSync5, existsSync as existsSync18, mkdirSync as mkdirSync20, openSync as openSync5, readdirSync as readdirSync9, readFileSync as readFileSync22, readSync as readSync5, renameSync as renameSync14, rmSync as rmSync12, statSync as statSync10, writeFileSync as writeFileSync20 } from "fs";
3953
4049
  import { createRequire as createRequire3 } from "module";
3954
- import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute2, join as join20 } from "path";
4050
+ import { basename as basename4, dirname as dirname12, isAbsolute as isAbsolute2, join as join21 } from "path";
3955
4051
 
3956
4052
  // src/core/launch.ts
3957
4053
  import { mkdirSync as mkdirSync18, readFileSync as readFileSync18, renameSync as renameSync12, rmSync as rmSync11, writeFileSync as writeFileSync18 } from "fs";
@@ -3960,7 +4056,7 @@ import { join as join17 } from "path";
3960
4056
  // src/core/wake.ts
3961
4057
  import { execFileSync as execFileSync8 } from "child_process";
3962
4058
  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";
4059
+ 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 statSync6, writeFileSync as writeFileSync17 } from "fs";
3964
4060
  import { tmpdir } from "os";
3965
4061
  import { join as join16 } from "path";
3966
4062
  function wakeDir(home = deskbuddyHome()) {
@@ -4108,7 +4204,7 @@ function analyzeTranscript(file3) {
4108
4204
  if (!file3) return null;
4109
4205
  let fd;
4110
4206
  try {
4111
- const size = statSync5(file3).size;
4207
+ const size = statSync6(file3).size;
4112
4208
  const len = Math.min(size, TRANSCRIPT_TAIL2);
4113
4209
  const buf = Buffer.alloc(len);
4114
4210
  fd = openSync2(file3, "r");
@@ -4216,16 +4312,16 @@ function launchPending(runId) {
4216
4312
  }
4217
4313
 
4218
4314
  // 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";
4315
+ import { closeSync as closeSync3, existsSync as existsSync15, openSync as openSync3, readFileSync as readFileSync19, readSync as readSync3, statSync as statSync7 } from "fs";
4220
4316
  import { createRequire as createRequire2 } from "module";
4221
4317
  import { homedir as homedir5 } from "os";
4222
4318
  import { basename as basename3, dirname as dirname10, isAbsolute, join as join18 } from "path";
4223
4319
 
4224
4320
  // src/core/dirFilter.ts
4225
4321
  import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
4226
- import { posix, win32 as win322 } from "path";
4322
+ import { posix, win32 as win323 } from "path";
4227
4323
  function norm(p, win) {
4228
- if (win) return win322.resolve(p).replace(/[\\/]+$/, "").toLowerCase();
4324
+ if (win) return win323.resolve(p).replace(/[\\/]+$/, "").toLowerCase();
4229
4325
  return posix.resolve(p).replace(/\/+$/, "");
4230
4326
  }
4231
4327
  function isUnder(p, base, win) {
@@ -4234,7 +4330,7 @@ function isUnder(p, base, win) {
4234
4330
  function isAutoWorkspace(p, opts = {}) {
4235
4331
  if (!p) return false;
4236
4332
  const win = (opts.platform ?? process.platform) === "win32";
4237
- const P = win ? win322 : posix;
4333
+ const P = win ? win323 : posix;
4238
4334
  const home = norm(opts.home ?? homedir4(), win);
4239
4335
  const full = norm(p, win);
4240
4336
  const name = P.basename(full);
@@ -4245,7 +4341,7 @@ function isAutoWorkspace(p, opts = {}) {
4245
4341
  function isReportableDir(p, opts = {}) {
4246
4342
  if (!p) return false;
4247
4343
  const win = (opts.platform ?? process.platform) === "win32";
4248
- const P = win ? win322 : posix;
4344
+ const P = win ? win323 : posix;
4249
4345
  const full = norm(p, win);
4250
4346
  if (full === norm(P.parse(P.resolve(p)).root, win)) return false;
4251
4347
  if (full === norm(opts.home ?? homedir4(), win)) return false;
@@ -4278,9 +4374,9 @@ function qoderDbPaths(tool, platform = process.platform, home = homedir5(), env
4278
4374
 
4279
4375
  // src/daemon/locate.ts
4280
4376
  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";
4377
+ import { accessSync, closeSync as closeSync4, constants, existsSync as existsSync17, lstatSync, openSync as openSync4, readFileSync as readFileSync21, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync9 } from "fs";
4282
4378
  import { homedir as homedir6 } from "os";
4283
- import { delimiter as delimiter2, dirname as dirname11, join as join19, win32 as win323 } from "path";
4379
+ import { delimiter as delimiter2, dirname as dirname11, join as join20, win32 as win325 } from "path";
4284
4380
 
4285
4381
  // src/core/agentEnv.ts
4286
4382
  var BUN_HIJACK_ENV = ["BUN_BE_BUN", "BUN_OPTIONS"];
@@ -4297,6 +4393,287 @@ function scrubForCommand(cmd, env = process.env) {
4297
4393
  return true;
4298
4394
  }
4299
4395
 
4396
+ // src/daemon/winAppLocator.ts
4397
+ import { existsSync as existsSync16, mkdirSync as mkdirSync19, readdirSync as readdirSync8, readFileSync as readFileSync20, renameSync as renameSync13, statSync as statSync8, writeFileSync as writeFileSync19 } from "fs";
4398
+ import { join as join19, win32 as win324 } from "path";
4399
+ var SOURCE_TEXT = {
4400
+ process: "\u6B63\u5728\u8FD0\u884C\u7684\u8FDB\u7A0B",
4401
+ remembered: "\u4E4B\u524D\u8BB0\u4E0B\u7684\u4F4D\u7F6E",
4402
+ uninstall: "\u6CE8\u518C\u8868\u5378\u8F7D\u9879",
4403
+ shortcut: "\u5FEB\u6377\u65B9\u5F0F",
4404
+ appPaths: "App Paths",
4405
+ protocol: "URL \u534F\u8BAE",
4406
+ default: "\u9ED8\u8BA4\u76EE\u5F55",
4407
+ path: "PATH"
4408
+ };
4409
+ var QODER_CN_WORKER_REL = "resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-cn-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
4410
+ var QODER_WORKER_REL = "resources/app.asar.unpacked/node_modules/@qoder-ai/qoder-agent-sdk/dist/_worker/qoder-worker-runtime.obf.mjs";
4411
+ function stdDirs(env, ...names) {
4412
+ const out = [];
4413
+ for (const n of names) {
4414
+ if (env.localAppData) out.push(win324.join(env.localAppData, "Programs", n));
4415
+ out.push(win324.join(env.programFiles || "C:\\Program Files", n));
4416
+ out.push(win324.join(env.programFilesX86 || "C:\\Program Files (x86)", n));
4417
+ }
4418
+ return out;
4419
+ }
4420
+ var WIN_APP_SPECS = {
4421
+ 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") },
4422
+ qoder: { id: "qoder", label: "Qoder", exe: "Qoder.exe", names: /^Qoder(?! CN)(\s|$)/i, protocols: ["qoder"], required: [QODER_WORKER_REL], defaultDirs: (e) => stdDirs(e, "Qoder") },
4423
+ // 与原 locateWorkbuddyAppExe 一致:DisplayName 含 WorkBuddy、只要 WorkBuddy.exe 在
4424
+ workbuddy: { id: "workbuddy", label: "WorkBuddy", exe: "WorkBuddy.exe", names: /WorkBuddy/i, protocols: ["workbuddy"], required: [], defaultDirs: (e) => stdDirs(e, "WorkBuddy") },
4425
+ qwenwork: { id: "qwenwork", label: "\u5343\u95EE\u529E\u516C", exe: "QwenWorkCN.exe", names: /千问办公|QwenWork/i, protocols: ["qwenwork-cn"], required: [], defaultDirs: (e) => stdDirs(e, "QwenWorkCN") },
4426
+ 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") }
4427
+ };
4428
+ var defaultLocFs = {
4429
+ exists: (p) => existsSync16(p),
4430
+ readText: (p) => {
4431
+ try {
4432
+ if (statSync8(p).size > 64 * 1024) return null;
4433
+ return readFileSync20(p, "utf8");
4434
+ } catch {
4435
+ return null;
4436
+ }
4437
+ },
4438
+ subdirs: (p) => {
4439
+ try {
4440
+ return readdirSync8(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
4441
+ } catch {
4442
+ return [];
4443
+ }
4444
+ }
4445
+ };
4446
+ function validExe(p, spec, fs) {
4447
+ if (!isValidWinPath(p) || win324.basename(p).toLowerCase() !== spec.exe.toLowerCase() || !fs.exists(p)) return false;
4448
+ const dir2 = win324.dirname(p);
4449
+ return spec.required.every((r) => fs.exists(win324.join(dir2, ...r.split("/"))));
4450
+ }
4451
+ function expandHint(hint, spec, fs, deep = true) {
4452
+ const out = [];
4453
+ const add = (p) => {
4454
+ if (p && isValidWinPath(p) && !out.some((x) => x.toLowerCase() === p.toLowerCase())) out.push(p);
4455
+ };
4456
+ const base = win324.basename(hint);
4457
+ if (base.toLowerCase() === spec.exe.toLowerCase()) add(hint);
4458
+ const dir2 = /\.[a-z0-9]{2,4}$/i.test(base) ? win324.dirname(hint) : hint;
4459
+ add(win324.join(dir2, spec.exe));
4460
+ const ini = fs.readText(win324.join(dir2, "install.ini"));
4461
+ if (ini) add(launcherTarget(ini, spec.exe));
4462
+ 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));
4463
+ return out;
4464
+ }
4465
+ var WIN_LOCATE_PS = String.raw`[Console]::OutputEncoding=[Text.Encoding]::UTF8
4466
+ # PowerShell 5.1 的 ConvertFrom-Json 把数组当一个对象输出,经 ForEach-Object 展开成元素(SF-Test 实测,否则 -contains 全不中)
4467
+ $exes = @($env:DB_LOC_EXES | ConvertFrom-Json | ForEach-Object { $_ } | ForEach-Object { [string]$_ }); $protos = @($env:DB_LOC_PROTOS | ConvertFrom-Json | ForEach-Object { $_ } | ForEach-Object { [string]$_ })
4468
+ $o = [ordered]@{ procs = @(); uninstall = @(); appPaths = @(); protocols = @() }
4469
+ try { $o.procs = @(Get-CimInstance Win32_Process -ErrorAction Stop | Where-Object { $exes -contains $_.Name } | ForEach-Object { [pscustomobject]@{ name = [string]$_.Name; path = [string]$_.ExecutablePath } }) } catch {}
4470
+ $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 } })
4471
+ 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 } } } }
4472
+ 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 } } } }
4473
+ ConvertTo-Json -InputObject $o -Compress -Depth 4`;
4474
+ var str = (v) => typeof v === "string" ? v : "";
4475
+ var arr = (v) => (Array.isArray(v) ? v : v && typeof v === "object" ? [v] : []).filter((x) => x && typeof x === "object");
4476
+ function parseFacts(out) {
4477
+ if (out === null) return null;
4478
+ try {
4479
+ const v = JSON.parse(out.trim());
4480
+ if (!v || typeof v !== "object") return null;
4481
+ return {
4482
+ procs: arr(v.procs).map((x) => ({ name: str(x.name), path: str(x.path) })),
4483
+ uninstall: arr(v.uninstall).map((x) => ({ name: str(x.name), il: str(x.il), icon: str(x.icon), un: str(x.un) })),
4484
+ appPaths: arr(v.appPaths).map((x) => ({ exe: str(x.exe), def: str(x.def), path: str(x.path) })),
4485
+ protocols: arr(v.protocols).map((x) => ({ name: str(x.name), cmd: str(x.cmd) }))
4486
+ };
4487
+ } catch {
4488
+ return null;
4489
+ }
4490
+ }
4491
+ function parseLnks(out) {
4492
+ if (out === null) return null;
4493
+ try {
4494
+ const v = JSON.parse(out.trim() || "[]");
4495
+ return arr(v).map((x) => ({ path: str(x.path), target: str(x.target) })).filter((x) => x.path && x.target);
4496
+ } catch {
4497
+ return null;
4498
+ }
4499
+ }
4500
+ function locateFromFacts(spec, facts, lnks, remembered, env, fs) {
4501
+ const exeLow = spec.exe.toLowerCase();
4502
+ const groups = [
4503
+ { source: "process", hints: (facts?.procs ?? []).filter((p) => p.name.toLowerCase() === exeLow).map((p) => p.path), failed: facts === null },
4504
+ { source: "remembered", hints: remembered ? [remembered] : [] },
4505
+ { source: "uninstall", hints: (facts?.uninstall ?? []).filter((u) => spec.names.test(u.name.trim())).flatMap((u) => [u.il, u.icon, u.un]), failed: facts === null },
4506
+ {
4507
+ source: "shortcut",
4508
+ hints: (lnks ?? []).filter((l) => win324.basename(l.target).toLowerCase() === exeLow || spec.names.test(win324.basename(l.path).replace(/\.lnk$/i, ""))).map((l) => l.target),
4509
+ failed: lnks === null
4510
+ },
4511
+ { source: "appPaths", hints: (facts?.appPaths ?? []).filter((a) => a.exe.toLowerCase() === exeLow).flatMap((a) => [a.def, a.path]), failed: facts === null },
4512
+ { source: "protocol", hints: (facts?.protocols ?? []).filter((p) => spec.protocols.includes(p.name.toLowerCase())).map((p) => p.cmd), failed: facts === null },
4513
+ { source: "default", hints: spec.defaultDirs(env) },
4514
+ { source: "path", hints: (env.path ?? "").split(";").filter((x) => x.trim()).map((x) => x.trim()) }
4515
+ ];
4516
+ const notes = [];
4517
+ for (const g of groups) {
4518
+ const hints = g.hints.map(normalizeHint).filter((h) => h !== null);
4519
+ 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: "" };
4520
+ if (g.source === "remembered" && hints.length === 0) continue;
4521
+ 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`;
4522
+ notes.push(`${SOURCE_TEXT[g.source]}\uFF1A${what}`);
4523
+ }
4524
+ return { found: null, tried: notes.join("\uFF1B") };
4525
+ }
4526
+ var WIN_APPS_FILE = "winapps.json";
4527
+ var LOCATE_FAIL_LOG_MS = 6 * 60 * 60 * 1e3;
4528
+ var LOCATE_MIN_GAP_MS = 60 * 1e3;
4529
+ var LOCATE_EVERY_MS = 10 * 60 * 1e3;
4530
+ var cacheFile = () => join19(deskbuddyHome(), WIN_APPS_FILE);
4531
+ function loadCache(file3 = cacheFile()) {
4532
+ try {
4533
+ const v = JSON.parse(readFileSync20(file3, "utf8"));
4534
+ const out = {};
4535
+ for (const [k, e] of Object.entries(v ?? {})) {
4536
+ const x = e;
4537
+ 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 };
4538
+ }
4539
+ return out;
4540
+ } catch {
4541
+ return {};
4542
+ }
4543
+ }
4544
+ function saveCache(c, file3) {
4545
+ try {
4546
+ mkdirSync19(join19(file3, ".."), { recursive: true });
4547
+ writeFileSync19(file3 + ".tmp", JSON.stringify(c, null, 2));
4548
+ renameSync13(file3 + ".tmp", file3);
4549
+ } catch {
4550
+ }
4551
+ }
4552
+ function cachedWinAppExe(id, fs = defaultLocFs, file3 = cacheFile()) {
4553
+ const spec = WIN_APP_SPECS[id];
4554
+ const e = spec ? loadCache(file3)[id] : void 0;
4555
+ return spec && e && validExe(e.exe, spec, fs) ? e.exe : null;
4556
+ }
4557
+ var WinAppLocator = class {
4558
+ #d;
4559
+ /** 正在进行的查询(只覆盖 ids 里的工具) */
4560
+ #inflight = null;
4561
+ /** 每个工具上次查完的时间(最小间隔按工具分别记) */
4562
+ #lastRunAt = /* @__PURE__ */ new Map();
4563
+ #logFn;
4564
+ #lastResult = {};
4565
+ #failLogged = /* @__PURE__ */ new Map();
4566
+ constructor(d = {}) {
4567
+ this.#d = d;
4568
+ this.#logFn = d.log;
4569
+ }
4570
+ /** 补上日志函数(先被不带日志的调用方建出来时,daemon 后给的 log 也要生效);已有的不覆盖 */
4571
+ setLog(log) {
4572
+ if (log && !this.#logFn) this.#logFn = log;
4573
+ }
4574
+ get hasLog() {
4575
+ return !!this.#logFn;
4576
+ }
4577
+ #now() {
4578
+ return this.#d.now ? this.#d.now() : Date.now();
4579
+ }
4580
+ #active() {
4581
+ if ((this.#d.platform ?? process.platform) !== "win32") return false;
4582
+ return !(this.#d.skip ? this.#d.skip() : process.env.DESKBUDDY_SKIP_WIN_LOCATE === "1");
4583
+ }
4584
+ #env() {
4585
+ return this.#d.env ?? { localAppData: process.env.LOCALAPPDATA, programFiles: process.env.ProgramFiles, programFilesX86: process.env["ProgramFiles(x86)"], path: process.env.PATH };
4586
+ }
4587
+ /**
4588
+ * 查一遍(一次 PowerShell 查进程 / 注册表 + 一次枚举快捷方式,异步带超时),结果写缓存。
4589
+ * 正在进行的查询已覆盖这些工具 → 共用它;没覆盖 → 等它结束再查自己这批(不并发起两次 PowerShell)
4590
+ */
4591
+ refresh(ids = Object.keys(WIN_APP_SPECS)) {
4592
+ if (!this.#active()) return Promise.resolve({});
4593
+ const want = ids.filter((i) => WIN_APP_SPECS[i]);
4594
+ const cur = this.#inflight;
4595
+ if (cur && want.every((i) => cur.ids.has(i))) return cur.p;
4596
+ const p = (cur ? cur.p.then(() => void 0, () => void 0) : Promise.resolve()).then(() => this.#run(want));
4597
+ const entry = { ids: new Set(want), p };
4598
+ this.#inflight = entry;
4599
+ void p.finally(() => {
4600
+ if (this.#inflight === entry) this.#inflight = null;
4601
+ }).catch(() => void 0);
4602
+ return p;
4603
+ }
4604
+ /** 某一个工具的主程序:只查这一个工具;它近 60 秒内查过就直接用缓存(正在查它就等查完)。非 Windows / 测试兜底返回缓存 */
4605
+ async locate(id) {
4606
+ const fs = this.#d.fs ?? defaultLocFs;
4607
+ const file3 = this.#d.file ?? cacheFile();
4608
+ if (this.#active() && WIN_APP_SPECS[id]) {
4609
+ const cur = this.#inflight;
4610
+ if (cur && cur.ids.has(id)) await cur.p.catch(() => void 0);
4611
+ else if (this.#now() - (this.#lastRunAt.get(id) ?? -Infinity) >= LOCATE_MIN_GAP_MS) await this.refresh([id]).catch(() => void 0);
4612
+ }
4613
+ return cachedWinAppExe(id, fs, file3);
4614
+ }
4615
+ async #run(ids) {
4616
+ const ps = this.#d.ps ?? defaultPs;
4617
+ const fs = this.#d.fs ?? defaultLocFs;
4618
+ const file3 = this.#d.file ?? cacheFile();
4619
+ const specs = ids.map((i) => WIN_APP_SPECS[i]);
4620
+ const facts = parseFacts(
4621
+ 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)
4622
+ );
4623
+ const lnks = parseLnks(await ps(LNK_LIST_PS).catch(() => null));
4624
+ const cache2 = loadCache(file3);
4625
+ const out = {};
4626
+ let dirty = false;
4627
+ for (const spec of specs) {
4628
+ const prev = cache2[spec.id];
4629
+ const r = locateFromFacts(spec, facts, lnks, prev?.exe ?? null, this.#env(), fs);
4630
+ out[spec.id] = r.found;
4631
+ if (r.found) {
4632
+ this.#failLogged.delete(spec.id);
4633
+ const changed = !prev || prev.exe.toLowerCase() !== r.found.exe.toLowerCase();
4634
+ 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`);
4635
+ if (changed || r.found.source !== "remembered") {
4636
+ cache2[spec.id] = { exe: r.found.exe, source: r.found.source, at: this.#now() };
4637
+ dirty = true;
4638
+ }
4639
+ } else {
4640
+ if (prev) {
4641
+ delete cache2[spec.id];
4642
+ dirty = true;
4643
+ }
4644
+ this.#noteMissing(spec, r.tried);
4645
+ }
4646
+ }
4647
+ if (dirty) saveCache(cache2, file3);
4648
+ for (const id of ids) this.#lastRunAt.set(id, this.#now());
4649
+ this.#lastResult = out;
4650
+ return out;
4651
+ }
4652
+ /** 同一原因 LOCATE_FAIL_LOG_MS 内只记一次(不刷屏) */
4653
+ #noteMissing(spec, tried) {
4654
+ const key = `${spec.id}\0${tried}`;
4655
+ const at = this.#failLogged.get(key);
4656
+ const now = this.#now();
4657
+ if (at !== void 0 && now - at < LOCATE_FAIL_LOG_MS) return;
4658
+ for (const k of this.#failLogged.keys()) if (k.startsWith(`${spec.id}\0`)) this.#failLogged.delete(k);
4659
+ this.#failLogged.set(key, now);
4660
+ this.#log(`\u672C\u673A\u627E\u4E0D\u5230 ${spec.label} \u684C\u9762\u7248\uFF08${tried}\uFF09`);
4661
+ }
4662
+ #log(msg) {
4663
+ this.#logFn?.(msg);
4664
+ }
4665
+ /** 测试用:上一轮结果 */
4666
+ get lastResult() {
4667
+ return this.#lastResult;
4668
+ }
4669
+ };
4670
+ var shared = null;
4671
+ function winAppLocator(log) {
4672
+ if (!shared) shared = new WinAppLocator({ log });
4673
+ else shared.setLog(log);
4674
+ return shared;
4675
+ }
4676
+
4300
4677
  // src/daemon/locate.ts
4301
4678
  function qoderWorker(d, worker, appBin) {
4302
4679
  if (d.isExecutable(appBin)) return { file: appBin, prefixArgs: [worker], env: { ELECTRON_RUN_AS_NODE: "1" } };
@@ -4308,14 +4685,14 @@ var defaultLocateDeps = () => ({
4308
4685
  nodePath: process.execPath,
4309
4686
  isExecutable: (p) => {
4310
4687
  try {
4311
- if (!statSync7(p).isFile()) return false;
4688
+ if (!statSync9(p).isFile()) return false;
4312
4689
  accessSync(p, constants.X_OK);
4313
4690
  return true;
4314
4691
  } catch {
4315
4692
  return false;
4316
4693
  }
4317
4694
  },
4318
- exists: (p) => existsSync16(p),
4695
+ exists: (p) => existsSync17(p),
4319
4696
  lexists: (p) => {
4320
4697
  try {
4321
4698
  lstatSync(p);
@@ -4329,15 +4706,16 @@ var defaultLocateDeps = () => ({
4329
4706
  localAppData: process.env.LOCALAPPDATA ?? "",
4330
4707
  readText: (p) => {
4331
4708
  try {
4332
- return readFileSync20(p, "utf8");
4709
+ return readFileSync21(p, "utf8");
4333
4710
  } catch {
4334
4711
  return null;
4335
4712
  }
4336
4713
  },
4337
4714
  // 🔴 daemon 自己也不能找到垫片:远程任务要的是真程序(窗口接管 spec 2026-09-30 §3.1)
4338
- skipDirs: [join19(deskbuddyHome(), "bin")],
4715
+ skipDirs: [join20(deskbuddyHome(), "bin")],
4339
4716
  winAppHints: (product, exe) => winInstallDirs(product, exe),
4340
4717
  winAppExeHints: () => winWorkbuddyExeHints(),
4718
+ winAppExe: (id) => cachedWinAppExe(id),
4341
4719
  isPe: (p) => {
4342
4720
  let fd = null;
4343
4721
  try {
@@ -4412,7 +4790,7 @@ function classifyAsyncError(e, stdout2, stderr) {
4412
4790
  var defaultProbe = createVersionProbe({
4413
4791
  stat: (p) => {
4414
4792
  try {
4415
- const st = statSync7(p);
4793
+ const st = statSync9(p);
4416
4794
  return { size: st.size, mtimeMs: st.mtimeMs };
4417
4795
  } catch {
4418
4796
  return null;
@@ -4460,9 +4838,9 @@ function installDirsFromProcs(procs, exe) {
4460
4838
  const out = [];
4461
4839
  for (const p of procs ?? []) {
4462
4840
  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);
4841
+ if (win325.basename(first).toLowerCase() !== exe.toLowerCase()) continue;
4842
+ const dir2 = win325.dirname(first);
4843
+ if (win325.isAbsolute(dir2) && !out.some((d) => d.toLowerCase() === dir2.toLowerCase())) out.push(dir2);
4466
4844
  }
4467
4845
  return out;
4468
4846
  }
@@ -4470,8 +4848,8 @@ function parseUninstallDirs(out) {
4470
4848
  const dirs = [];
4471
4849
  for (const raw of out.split(/\r?\n/)) {
4472
4850
  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);
4851
+ if (!v || !win325.isAbsolute(v)) continue;
4852
+ if (/\.exe$/i.test(v)) v = win325.dirname(v);
4475
4853
  v = v.replace(/[\\/]+$/, "");
4476
4854
  if (!dirs.some((d) => d.toLowerCase() === v.toLowerCase())) dirs.push(v);
4477
4855
  }
@@ -4498,7 +4876,7 @@ function parseExeHints(out) {
4498
4876
  const res = [];
4499
4877
  for (const raw of out.split(/\r?\n/)) {
4500
4878
  const v = raw.trim().replace(/,\s*-?\d+$/, "").replace(/^"|"$/g, "").trim().replace(/[\\/]+$/, "");
4501
- if (!v || !win323.isAbsolute(v)) continue;
4879
+ if (!v || !win325.isAbsolute(v)) continue;
4502
4880
  if (!res.some((x) => x.toLowerCase() === v.toLowerCase())) res.push(v);
4503
4881
  }
4504
4882
  return res;
@@ -4522,57 +4900,54 @@ function winWorkbuddyExeHints() {
4522
4900
  function locateWorkbuddyAppExe(d = defaultLocateDeps()) {
4523
4901
  if (!isWin(d)) {
4524
4902
  for (const dir2 of appDirs(d.home)) {
4525
- const p = join19(dir2, "WorkBuddy.app");
4903
+ const p = join20(dir2, "WorkBuddy.app");
4526
4904
  if (d.exists(p)) return p;
4527
4905
  }
4528
4906
  return null;
4529
4907
  }
4530
- return pickWorkbuddyExe(winAppDirs(d, "WorkBuddy"), d) ?? pickWorkbuddyExe(d.winAppExeHints?.() ?? [], d);
4908
+ return pickWorkbuddyExe(locatedExe(d, "workbuddy"), d) ?? pickWorkbuddyExe(winAppDirs(d, "WorkBuddy"), d) ?? pickWorkbuddyExe(d.winAppExeHints?.() ?? [], d);
4909
+ }
4910
+ function locatedExe(d, id) {
4911
+ const p = d.winAppExe?.(id);
4912
+ return p ? [p] : [];
4913
+ }
4914
+ function locatedDirs(d, id) {
4915
+ return locatedExe(d, id).map((p) => win325.dirname(p));
4531
4916
  }
4532
4917
  function pickWorkbuddyExe(cands, d) {
4533
4918
  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;
4919
+ const exe = /\.exe$/i.test(c) ? c : win325.join(c, "WorkBuddy.exe");
4920
+ if (win325.basename(exe).toLowerCase() === "workbuddy.exe" && d.exists(exe)) return exe;
4536
4921
  }
4537
4922
  return null;
4538
4923
  }
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
- });
4924
+ async function winWorkbuddyLocatorHints() {
4925
+ const p = await winAppLocator().locate("workbuddy");
4926
+ return p ? [p] : [];
4552
4927
  }
4553
- async function locateWorkbuddyAppExeAsync(d = defaultLocateDeps(), hints = winWorkbuddyExeHintsAsync) {
4928
+ async function locateWorkbuddyAppExeAsync(d = defaultLocateDeps(), hints = winWorkbuddyLocatorHints) {
4554
4929
  if (!isWin(d)) return locateWorkbuddyAppExe(d);
4555
- return pickWorkbuddyExe(winAppDirs(d, "WorkBuddy"), d) ?? pickWorkbuddyExe(await hints(), d);
4930
+ return pickWorkbuddyExe(locatedExe(d, "workbuddy"), d) ?? pickWorkbuddyExe(winAppDirs(d, "WorkBuddy"), d) ?? pickWorkbuddyExe(await hints(), d);
4556
4931
  }
4557
4932
  var isWin = (d) => (d.platform ?? process.platform) === "win32";
4558
4933
  var WIN_EXTS = [".exe", ".cmd", ".bat"];
4559
4934
  function extraBinDirs(home, nodePath = "", win) {
4560
4935
  if (win) {
4561
4936
  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"),
4937
+ join20(home, ".local", "bin"),
4938
+ ...win.appData ? [join20(win.appData, "npm")] : [],
4939
+ join20(home, ".bun", "bin"),
4940
+ ...win.localAppData ? [join20(win.localAppData, "Volta", "bin")] : [],
4941
+ join20(home, "scoop", "shims"),
4567
4942
  ...nodePath ? [dirname11(nodePath)] : []
4568
4943
  ];
4569
4944
  }
4570
4945
  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"),
4946
+ join20(home, ".local", "bin"),
4947
+ join20(home, ".claude", "local"),
4948
+ join20(home, ".npm-global", "bin"),
4949
+ join20(home, ".bun", "bin"),
4950
+ join20(home, ".volta", "bin"),
4576
4951
  "/opt/homebrew/bin",
4577
4952
  "/usr/local/bin",
4578
4953
  ...nodePath ? [dirname11(nodePath)] : []
@@ -4586,7 +4961,7 @@ function whichAllIn(name, d) {
4586
4961
  const out = [];
4587
4962
  for (const dir2 of binDirs(d)) {
4588
4963
  for (const ext of exts) {
4589
- const p = join19(dir2, name + ext);
4964
+ const p = join20(dir2, name + ext);
4590
4965
  if (d.isExecutable(p)) out.push(p);
4591
4966
  }
4592
4967
  }
@@ -4610,7 +4985,7 @@ function binDirs(d) {
4610
4985
  function parseNpmCmdShim(shimPath, text) {
4611
4986
  const m = text.match(/"%~?dp0%?\\?([^"%]+)"\s+%\*/i);
4612
4987
  if (!m) return null;
4613
- return join19(dirname11(shimPath), m[1].replace(/^\\/, ""));
4988
+ return join20(dirname11(shimPath), m[1].replace(/^\\/, ""));
4614
4989
  }
4615
4990
  var JS_RUNTIME_EXE = /^(?:node|bun|bunx|deno)(?:\.exe)?$/i;
4616
4991
  function resolveWinLaunch(file3, d) {
@@ -4621,7 +4996,7 @@ function resolveWinLaunch(file3, d) {
4621
4996
  const target = text ? parseNpmCmdShim(file3, text) : null;
4622
4997
  if (!target || !d.exists(target)) return null;
4623
4998
  if (/\.(?:cmd|bat)$/i.test(target)) return null;
4624
- if (JS_RUNTIME_EXE.test(win323.basename(target.replace(/\//g, "\\")))) return null;
4999
+ if (JS_RUNTIME_EXE.test(win325.basename(target.replace(/\//g, "\\")))) return null;
4625
5000
  return resolveWinLaunch(target, d);
4626
5001
  }
4627
5002
  if (/\.(?:c|m)?js$/.test(lower)) return { file: d.nodePath, prefixArgs: [file3] };
@@ -4630,22 +5005,22 @@ function resolveWinLaunch(file3, d) {
4630
5005
  return null;
4631
5006
  }
4632
5007
  function appDirs(home) {
4633
- return ["/Applications", join19(home, "Applications")];
5008
+ return ["/Applications", join20(home, "Applications")];
4634
5009
  }
4635
5010
  function winAppDirs(d, product) {
4636
5011
  return [
4637
- ...d.localAppData ? [join19(d.localAppData, "Programs", product)] : [],
4638
- join19("C:\\Program Files", product),
4639
- join19("C:\\Program Files (x86)", product)
5012
+ ...d.localAppData ? [join20(d.localAppData, "Programs", product)] : [],
5013
+ join20("C:\\Program Files", product),
5014
+ join20("C:\\Program Files (x86)", product)
4640
5015
  ];
4641
5016
  }
4642
5017
  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";
5018
+ var QODER_CN_WORKER_IN_WIN_APP = QODER_CN_WORKER_REL;
5019
+ var QODER_WORKER_IN_WIN_APP = QODER_WORKER_REL;
4645
5020
  function winClaudeExeCandidates(d) {
4646
5021
  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")] : []
5022
+ ...d.home ? [join20(d.home, ".local", "bin", "claude.exe")] : [],
5023
+ ...d.appData ? [join20(d.appData, "npm", "node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe")] : []
4649
5024
  ];
4650
5025
  }
4651
5026
  function winClaudeLaunches(d) {
@@ -4690,21 +5065,21 @@ function locateWin(tool, d) {
4690
5065
  case "workbuddy":
4691
5066
  return locateWorkbuddy(d)?.loc ?? null;
4692
5067
  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"));
5068
+ for (const dir2 of [...locatedDirs(d, "qoder_cn"), ...winAppDirs(d, "Qoder CN")]) {
5069
+ const p = join20(dir2, QODER_CN_WORKER_IN_WIN_APP);
5070
+ if (d.exists(p)) return qoderWorker(d, p, join20(dir2, "Qoder CN.exe"));
4696
5071
  }
4697
5072
  return plain("qoderclicn");
4698
5073
  }
4699
5074
  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"));
5075
+ for (const dir2 of [...locatedDirs(d, "qoder"), ...winAppDirs(d, "Qoder")]) {
5076
+ const p = join20(dir2, QODER_WORKER_IN_WIN_APP);
5077
+ if (d.exists(p)) return qoderWorker(d, p, join20(dir2, "Qoder.exe"));
4703
5078
  }
4704
5079
  return plain("qodercli");
4705
5080
  }
4706
5081
  case "opencode": {
4707
- const p = join19(d.home, ".opencode", "bin", "opencode.exe");
5082
+ const p = join20(d.home, ".opencode", "bin", "opencode.exe");
4708
5083
  if (d.exists(p)) return { file: p, prefixArgs: [] };
4709
5084
  return plain("opencode");
4710
5085
  }
@@ -4716,7 +5091,7 @@ function locateWin(tool, d) {
4716
5091
  return null;
4717
5092
  }
4718
5093
  }
4719
- function workbuddyHasTui(loc, exists = existsSync16, realpath = realpathSync2) {
5094
+ function workbuddyHasTui(loc, exists = existsSync17, realpath = realpathSync2) {
4720
5095
  const real = (p) => {
4721
5096
  try {
4722
5097
  return realpath(p);
@@ -4728,8 +5103,8 @@ function workbuddyHasTui(loc, exists = existsSync16, realpath = realpathSync2) {
4728
5103
  const cands = [...loc.prefixArgs, loc.file].map(real);
4729
5104
  const script = cands.find((a) => BIN.test(a));
4730
5105
  if (!script) return true;
4731
- const dist = join19(dirname11(dirname11(script)), "dist");
4732
- return exists(join19(dist, "codebuddy.js"));
5106
+ const dist = join20(dirname11(dirname11(script)), "dist");
5107
+ return exists(join20(dist, "codebuddy.js"));
4733
5108
  }
4734
5109
  function locateWorkbuddyTui(d = defaultLocateDeps(), exists, realpath) {
4735
5110
  const cands = [locateWorkbuddyStandalone(d), locateWorkbuddyApp(d)].filter((c) => c !== null);
@@ -4770,7 +5145,7 @@ function locateWorkbuddyStandalone(d = defaultLocateDeps()) {
4770
5145
  function locateWorkbuddyApp(d = defaultLocateDeps()) {
4771
5146
  if (!isWin(d)) {
4772
5147
  for (const dir2 of appDirs(d.home)) {
4773
- const p = join19(dir2, WORKBUDDY_CLI_IN_APP);
5148
+ const p = join20(dir2, WORKBUDDY_CLI_IN_APP);
4774
5149
  if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
4775
5150
  }
4776
5151
  return null;
@@ -4778,7 +5153,7 @@ function locateWorkbuddyApp(d = defaultLocateDeps()) {
4778
5153
  const inApp = (dirs) => {
4779
5154
  for (const dir2 of dirs) {
4780
5155
  for (const ext of [".exe", ".cmd", ""]) {
4781
- const p = join19(dir2, WORKBUDDY_CLI_IN_WIN_APP + ext);
5156
+ const p = join20(dir2, WORKBUDDY_CLI_IN_WIN_APP + ext);
4782
5157
  if (d.exists(p)) {
4783
5158
  const r = resolveWinLaunch(p, d);
4784
5159
  if (r) return r;
@@ -4787,7 +5162,7 @@ function locateWorkbuddyApp(d = defaultLocateDeps()) {
4787
5162
  }
4788
5163
  return null;
4789
5164
  };
4790
- return inApp(winAppDirs(d, "WorkBuddy")) ?? inApp(d.winAppHints?.("WorkBuddy", "WorkBuddy.exe") ?? []);
5165
+ return inApp([...locatedDirs(d, "workbuddy"), ...winAppDirs(d, "WorkBuddy")]) ?? inApp(d.winAppHints?.("WorkBuddy", "WorkBuddy.exe") ?? []);
4791
5166
  }
4792
5167
  function locateWorkbuddy(d = defaultLocateDeps()) {
4793
5168
  const s = locateWorkbuddyStandalone(d);
@@ -4813,20 +5188,20 @@ function locate(tool, d = defaultLocateDeps()) {
4813
5188
  return locateWorkbuddy(d)?.loc ?? null;
4814
5189
  case "qoder_cn": {
4815
5190
  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"));
5191
+ const p = join20(dir2, QODER_CN_WORKER_IN_APP);
5192
+ if (d.exists(p)) return qoderWorker(d, p, join20(dir2, "Qoder CN.app", "Contents", "MacOS", "Qoder CN"));
4818
5193
  }
4819
5194
  return plain("qoderclicn");
4820
5195
  }
4821
5196
  case "qoder": {
4822
5197
  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"));
5198
+ const p = join20(dir2, QODER_WORKER_IN_APP);
5199
+ if (d.exists(p)) return qoderWorker(d, p, join20(dir2, "Qoder.app", "Contents", "MacOS", "Qoder"));
4825
5200
  }
4826
5201
  return plain("qodercli");
4827
5202
  }
4828
5203
  case "opencode": {
4829
- const p = join19(d.home, ".opencode", "bin", "opencode");
5204
+ const p = join20(d.home, ".opencode", "bin", "opencode");
4830
5205
  if (d.isExecutable(p)) return { file: p, prefixArgs: [] };
4831
5206
  return plain("opencode");
4832
5207
  }
@@ -4847,7 +5222,7 @@ function findWt(d = defaultLocateDeps()) {
4847
5222
  if (!isWin(d)) return null;
4848
5223
  const hit = whichIn("wt", d);
4849
5224
  if (hit) return hit;
4850
- const alias = d.localAppData ? join19(d.localAppData, "Microsoft", "WindowsApps", "wt.exe") : "";
5225
+ const alias = d.localAppData ? join20(d.localAppData, "Microsoft", "WindowsApps", "wt.exe") : "";
4851
5226
  return alias && (d.exists(alias) || (d.lexists?.(alias) ?? false)) ? alias : null;
4852
5227
  }
4853
5228
 
@@ -4914,13 +5289,13 @@ function findWbTranscript(configDir, sessionId) {
4914
5289
  if (!SESSION_UUID_RE.test(sessionId)) return null;
4915
5290
  let dirs;
4916
5291
  try {
4917
- dirs = readdirSync8(join20(configDir, "projects"));
5292
+ dirs = readdirSync9(join21(configDir, "projects"));
4918
5293
  } catch {
4919
5294
  return null;
4920
5295
  }
4921
5296
  for (const d of dirs) {
4922
- const p = join20(configDir, "projects", d, `${sessionId}.jsonl`);
4923
- if (existsSync17(p)) return p;
5297
+ const p = join21(configDir, "projects", d, `${sessionId}.jsonl`);
5298
+ if (existsSync18(p)) return p;
4924
5299
  }
4925
5300
  return null;
4926
5301
  }
@@ -4928,7 +5303,7 @@ var SLICE = 64 * 1024;
4928
5303
  function readSlice(p, from) {
4929
5304
  const fd = openSync5(p, "r");
4930
5305
  try {
4931
- const size = statSync8(p).size;
5306
+ const size = statSync10(p).size;
4932
5307
  const len = Math.min(size, SLICE);
4933
5308
  const buf = Buffer.alloc(len);
4934
5309
  readSync5(fd, buf, 0, len, from === "head" ? 0 : size - len);
@@ -4972,7 +5347,7 @@ function userText2(o) {
4972
5347
  function wbTranscriptInfo(p) {
4973
5348
  const head = jsonLines(readSlice(p, "head"));
4974
5349
  const tail = jsonLines(readSlice(p, "tail"));
4975
- const mtime = Math.floor(statSync8(p).mtimeMs);
5350
+ const mtime = Math.floor(statSync10(p).mtimeMs);
4976
5351
  const hs = head.map((o) => tsOf(o.timestamp)).filter((n) => n !== null);
4977
5352
  const ts = tail.map((o) => tsOf(o.timestamp)).filter((n) => n !== null);
4978
5353
  const createdAt = hs.length ? Math.min(...hs) : mtime;
@@ -5005,8 +5380,8 @@ function wbElectronCandidates(appCliPath) {
5005
5380
  const m = /^(.*?)[\\/]resources[\\/]app\.asar\.unpacked[\\/]/i.exec(appCliPath);
5006
5381
  if (!m) return [];
5007
5382
  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")];
5383
+ if (/\.app[\\/]Contents$/.test(root)) return [join21(root, "MacOS", "Electron"), join21(root, "MacOS", "WorkBuddy")];
5384
+ return [join21(root, "WorkBuddy.exe")];
5010
5385
  }
5011
5386
  function defaultWbElectron(d = defaultLocateDeps()) {
5012
5387
  if ((d.platform ?? process.platform) === "win32") return null;
@@ -5049,11 +5424,11 @@ function defaultAppRunning(platform = process.platform) {
5049
5424
  var PENDING_MAX = 20;
5050
5425
  var PENDING_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
5051
5426
  function pendingPath() {
5052
- return join20(deskbuddyHome(), "workbuddy-pending.json");
5427
+ return join21(deskbuddyHome(), "workbuddy-pending.json");
5053
5428
  }
5054
5429
  function loadPendingFile() {
5055
5430
  try {
5056
- const v = JSON.parse(readFileSync21(pendingPath(), "utf8"));
5431
+ const v = JSON.parse(readFileSync22(pendingPath(), "utf8"));
5057
5432
  return Array.isArray(v) ? v.filter((j) => j && typeof j.sessionId === "string" && typeof j.cwd === "string") : [];
5058
5433
  } catch {
5059
5434
  return [];
@@ -5065,10 +5440,10 @@ function savePendingFile(jobs) {
5065
5440
  rmSync12(pendingPath(), { force: true });
5066
5441
  return;
5067
5442
  }
5068
- mkdirSync19(deskbuddyHome(), { recursive: true });
5443
+ mkdirSync20(deskbuddyHome(), { recursive: true });
5069
5444
  const tmp = pendingPath() + ".tmp";
5070
- writeFileSync19(tmp, JSON.stringify(jobs), { mode: 384 });
5071
- renameSync13(tmp, pendingPath());
5445
+ writeFileSync20(tmp, JSON.stringify(jobs), { mode: 384 });
5446
+ renameSync14(tmp, pendingPath());
5072
5447
  } catch {
5073
5448
  }
5074
5449
  }
@@ -5173,7 +5548,7 @@ async function registerNow(job, d) {
5173
5548
  if (!SESSION_UUID_RE.test(job.sessionId)) return "skip bad_session";
5174
5549
  if (!job.cwd || !isAbsolute2(job.cwd)) return "skip bad_cwd";
5175
5550
  const db = d.dbPath();
5176
- if (!db || !existsSync17(db)) return "skip no_db";
5551
+ if (!db || !existsSync18(db)) return "skip no_db";
5177
5552
  const tp = findWbTranscript(d.configDir(), job.sessionId);
5178
5553
  if (!tp) return "skip no_transcript";
5179
5554
  const info = wbTranscriptInfo(tp);
@@ -5305,7 +5680,7 @@ var WsCdpClient = class {
5305
5680
  };
5306
5681
 
5307
5682
  // src/daemon/workbuddyApp/keeper.ts
5308
- import { posix as posix2, win32 as win324 } from "path";
5683
+ import { posix as posix2, win32 as win326 } from "path";
5309
5684
  var DEFAULT_CDP_PORT = 39217;
5310
5685
  var ENV_NAME = "WORKBUDDY_REMOTE_DEBUGGING_PORT";
5311
5686
  var RESTART_GAP_MS = 30 * 60 * 1e3;
@@ -5367,21 +5742,21 @@ function cleanLaunchEnv(src = process.env, shim = shimDir()) {
5367
5742
  }
5368
5743
  var REMEMBER_EXE_MS = 30 * 60 * 1e3;
5369
5744
  var AWAY_IDLE_MS = 5 * 60 * 1e3;
5370
- var keeperThrottleFile = () => join21(deskbuddyHome(), "wbapp", "keeper.json");
5745
+ var keeperThrottleFile = () => join22(deskbuddyHome(), "wbapp", "keeper.json");
5371
5746
  var filePersist = {
5372
5747
  load: () => {
5373
5748
  try {
5374
- return JSON.parse(readFileSync22(keeperThrottleFile(), "utf8"));
5749
+ return JSON.parse(readFileSync23(keeperThrottleFile(), "utf8"));
5375
5750
  } catch {
5376
5751
  return null;
5377
5752
  }
5378
5753
  },
5379
5754
  save: (s) => {
5380
5755
  try {
5381
- mkdirSync20(join21(deskbuddyHome(), "wbapp"), { recursive: true });
5756
+ mkdirSync21(join22(deskbuddyHome(), "wbapp"), { recursive: true });
5382
5757
  const f = keeperThrottleFile();
5383
- writeFileSync20(f + ".tmp", JSON.stringify(s));
5384
- renameSync14(f + ".tmp", f);
5758
+ writeFileSync21(f + ".tmp", JSON.stringify(s));
5759
+ renameSync15(f + ".tmp", f);
5385
5760
  } catch {
5386
5761
  }
5387
5762
  }
@@ -5413,7 +5788,7 @@ function defaultWorkbuddyAppPath() {
5413
5788
  }
5414
5789
  function isValidAppPath(p, platform = process.platform) {
5415
5790
  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";
5791
+ if (platform === "win32") return win326.isAbsolute(p) && /^[a-z]:[\\/]/i.test(p) && win326.basename(p).toLowerCase() === "workbuddy.exe";
5417
5792
  return posix2.isAbsolute(p) && posix2.basename(p) === "WorkBuddy.app";
5418
5793
  }
5419
5794
  var HUNG_RESTART_MS = 20 * 60 * 1e3;
@@ -5561,11 +5936,11 @@ function winMainProc(procs) {
5561
5936
  return null;
5562
5937
  }
5563
5938
  function workbuddyBusy(now = Date.now(), dir2 = stateDir(), alive = isAlive) {
5564
- if (!existsSync18(dir2)) return false;
5565
- for (const f of readdirSync9(dir2)) {
5939
+ if (!existsSync19(dir2)) return false;
5940
+ for (const f of readdirSync10(dir2)) {
5566
5941
  if (!f.endsWith(".json")) continue;
5567
5942
  try {
5568
- const s = JSON.parse(readFileSync22(join21(dir2, f), "utf8"));
5943
+ const s = JSON.parse(readFileSync23(join22(dir2, f), "utf8"));
5569
5944
  if (s.tool !== "workbuddy" || s.state !== "watch" && s.state !== "ask_approval") continue;
5570
5945
  if (typeof s.pid === "number" && s.pid > 0) {
5571
5946
  if (alive(s.pid, s.pidName)) return true;
@@ -5883,7 +6258,7 @@ var CdpKeeper = class {
5883
6258
  this.#loadThrottle();
5884
6259
  let path = null;
5885
6260
  try {
5886
- const exists = this.#d.exists ?? existsSync18;
6261
+ const exists = this.#d.exists ?? existsSync19;
5887
6262
  if (this.#appExe && isValidAppPath(this.#appExe, this.#platform()) && exists(this.#appExe)) path = this.#appExe;
5888
6263
  else {
5889
6264
  const found = await (this.#d.appPath ? this.#d.appPath() : defaultWorkbuddyAppPath());
@@ -6099,12 +6474,12 @@ var TYPE_TEXT = "text";
6099
6474
  var TYPE_SUMMARY = "summary";
6100
6475
  var MAX_TEXT_LENGTH = 2e3;
6101
6476
  var MAX_ID_LENGTH = 64;
6102
- function str(v) {
6477
+ function str2(v) {
6103
6478
  return typeof v === "string" ? v : "";
6104
6479
  }
6105
6480
  function parseMillis(v) {
6106
6481
  if (typeof v === "number" && Number.isFinite(v)) return v;
6107
- const s = str(v).trim();
6482
+ const s = str2(v).trim();
6108
6483
  if (!/^\d{1,16}$/.test(s)) return 0;
6109
6484
  const n = Number(s);
6110
6485
  return Number.isSafeInteger(n) ? n : 0;
@@ -6117,21 +6492,21 @@ function sanitizeText(raw) {
6117
6492
  }
6118
6493
  function parseCommand(frame) {
6119
6494
  const f = frame;
6120
- if (!f || str(f.service) !== SERVICE_COMMAND) return null;
6495
+ if (!f || str2(f.service) !== SERVICE_COMMAND) return null;
6121
6496
  const d = f.data;
6122
6497
  if (!d || typeof d !== "object") return null;
6123
- const instructionId = str(d.instructionId);
6498
+ const instructionId = str2(d.instructionId);
6124
6499
  if (!isValidId(instructionId)) return null;
6125
- const type = str(d.type);
6500
+ const type = str2(d.type);
6126
6501
  if (type !== TYPE_TEXT && type !== TYPE_SUMMARY) return null;
6127
- const tool = str(d.tool);
6502
+ const tool = str2(d.tool);
6128
6503
  if (!isValidId(tool)) return null;
6129
- const sessionId = str(d.sessionId);
6504
+ const sessionId = str2(d.sessionId);
6130
6505
  if (sessionId && !isValidId(sessionId)) return null;
6131
6506
  if (type === TYPE_SUMMARY && !sessionId) return null;
6132
- const text = sanitizeText(str(d.text));
6507
+ const text = sanitizeText(str2(d.text));
6133
6508
  if (!text || text.length > MAX_TEXT_LENGTH) return null;
6134
- const rawDir = str(d.dirRef);
6509
+ const rawDir = str2(d.dirRef);
6135
6510
  const dirRef = !sessionId && /^[0-9a-f]{16}$/.test(rawDir) ? rawDir : "";
6136
6511
  const cmd = { instructionId, type, sessionId, tool, text, expireAt: parseMillis(d.expireAt), dirRef };
6137
6512
  if (type === TYPE_SUMMARY) cmd.kind = "summary";
@@ -6151,14 +6526,14 @@ function cleanFeedback(v) {
6151
6526
  }
6152
6527
  function parseApprovalFrame(frame) {
6153
6528
  const f = frame;
6154
- if (!f || str(f.service) !== SERVICE_COMMAND) return null;
6529
+ if (!f || str2(f.service) !== SERVICE_COMMAND) return null;
6155
6530
  const d = f.data;
6156
- if (!d || typeof d !== "object" || str(d.type) !== TYPE_APPROVAL) return null;
6157
- const instructionId = str(d.instructionId);
6531
+ if (!d || typeof d !== "object" || str2(d.type) !== TYPE_APPROVAL) return null;
6532
+ const instructionId = str2(d.instructionId);
6158
6533
  if (!isValidId(instructionId)) return null;
6159
- const approvalId = str(d.approvalId);
6534
+ const approvalId = str2(d.approvalId);
6160
6535
  if (!isValidId(approvalId)) return null;
6161
- const decision = str(d.decision);
6536
+ const decision = str2(d.decision);
6162
6537
  if (decision !== "allow" && decision !== "deny" && decision !== "allow_always") return null;
6163
6538
  return {
6164
6539
  instructionId,
@@ -6178,6 +6553,11 @@ function optionFields(d) {
6178
6553
  function cleanDeviceName(v) {
6179
6554
  return typeof v === "string" ? v.replace(/[\u0000-\u001f\u007f]/g, "").trim().slice(0, 32) : "";
6180
6555
  }
6556
+ var APP_WAIT_MARGIN_MS = 20 * 1e3;
6557
+ function appWaitBudget(expireAt, now, max) {
6558
+ if (!(expireAt > 0)) return max;
6559
+ return Math.min(max, expireAt - now - APP_WAIT_MARGIN_MS);
6560
+ }
6181
6561
  function isExpired(cmd, now = Date.now()) {
6182
6562
  return cmd.expireAt > 0 && now > cmd.expireAt;
6183
6563
  }
@@ -6408,6 +6788,12 @@ var QoderAppTasks = class {
6408
6788
  async #ensureReady(cmd, sid, q, report, mayWake) {
6409
6789
  if (this.available()) return true;
6410
6790
  if (!mayWake || !this.#d.wake || !this.canWake()) return false;
6791
+ const raw = appWaitBudget(cmd.expireAt, this.#d.now(), QODER_APP_WAKE_WAIT_MS);
6792
+ const budget = Math.max(0, raw);
6793
+ if (raw <= 0 && !sid) {
6794
+ 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`);
6795
+ return false;
6796
+ }
6411
6797
  let w;
6412
6798
  q.wakeAt = this.#d.now();
6413
6799
  try {
@@ -6425,13 +6811,13 @@ var QoderAppTasks = class {
6425
6811
  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
6812
  report("queued", sid);
6427
6813
  }
6428
- const end = this.#d.now() + QODER_APP_WAKE_WAIT_MS;
6814
+ const end = q.wakeAt + budget;
6429
6815
  while (this.#d.now() < end) {
6430
6816
  this.#d.kick?.();
6431
- await this.#sleep(this.#d.pollMs ?? QODER_APP_POLL_MS);
6817
+ await this.#sleep(Math.max(1, Math.min(this.#d.pollMs ?? QODER_APP_POLL_MS, end - this.#d.now())));
6432
6818
  if (this.available()) return true;
6433
6819
  }
6434
- this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} ${this.#d.label} App ${QODER_APP_WAKE_WAIT_MS / 1e3} \u79D2\u5185\u6CA1\u8FDE\u4E0A`);
6820
+ 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
6821
  return false;
6436
6822
  }
6437
6823
  /** 只给测试和诊断用 */
@@ -6918,17 +7304,17 @@ var hasArg = (cc) => Object.values(cc.tool_input).some((v) => typeof v === "stri
6918
7304
  function dumateAskCc(p, call2) {
6919
7305
  if (call2 && hasArg(call2.cc)) return call2.cc;
6920
7306
  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);
7307
+ const str3 = (v) => typeof v === "string" ? v : "";
7308
+ const file3 = str3(md.filepath) || str3(md.filePath) || str3(md.hostPath);
7309
+ const dir2 = str3(md.parentDir) || str3(md.hostParentDir);
6924
7310
  const patterns = Array.isArray(p.patterns) ? p.patterns.filter((x) => typeof x === "string").join(" ") : "";
6925
7311
  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) });
7312
+ const cc = toCcTool(call2.tool, { filePath: file3, command: str3(md.command), path: dir2 || patterns, pattern: str3(md.pattern), url: str3(md.url) });
6927
7313
  if (hasArg(cc)) return cc;
6928
7314
  }
6929
7315
  if (p.permission === "external_directory") {
6930
7316
  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 } };
7317
+ return { tool_name: str3(display.title) || "\u8BBF\u95EE\u5DE5\u4F5C\u533A\u5916\u76EE\u5F55", tool_input: { path: file3 || dir2 || patterns } };
6932
7318
  }
6933
7319
  return askToCc(p);
6934
7320
  }
@@ -6936,7 +7322,7 @@ function scanSessionDirs(root, now, maxAgeMs = ACTIVE_DIR_MS) {
6936
7322
  const out = [];
6937
7323
  const list = (p) => {
6938
7324
  try {
6939
- return readdirSync10(p, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join22(p, d.name));
7325
+ return readdirSync11(p, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => join23(p, d.name));
6940
7326
  } catch {
6941
7327
  return [];
6942
7328
  }
@@ -6945,7 +7331,7 @@ function scanSessionDirs(root, now, maxAgeMs = ACTIVE_DIR_MS) {
6945
7331
  for (const day of list(u).sort().reverse().slice(0, 2))
6946
7332
  for (const dir2 of list(day)) {
6947
7333
  try {
6948
- const at = statSync9(dir2).mtimeMs;
7334
+ const at = statSync11(dir2).mtimeMs;
6949
7335
  if (now - at <= maxAgeMs) out.push({ dir: dir2, at });
6950
7336
  } catch {
6951
7337
  }
@@ -7088,14 +7474,14 @@ var DumateWatch = class {
7088
7474
  return this.#d.alt.subscribe;
7089
7475
  }
7090
7476
  /** 设备新建 / 回复用的目标:可用就用;不可用按规则唤起并等连上(ensure);还是不行返回原因码 */
7091
- async #ensureTarget() {
7477
+ async #ensureTarget(expireAt = 0) {
7092
7478
  const pick = () => this.#discover(false);
7093
7479
  let t = this.#target ?? pick();
7094
7480
  if (t) return t;
7095
7481
  let why = APP_NOT_RUNNING;
7096
7482
  if (this.#d.ensure) {
7097
7483
  try {
7098
- why = await this.#d.ensure();
7484
+ why = await this.#d.ensure(expireAt);
7099
7485
  } catch {
7100
7486
  why = APP_NOT_RUNNING;
7101
7487
  }
@@ -7110,12 +7496,12 @@ var DumateWatch = class {
7110
7496
  * model / agent 取法同回复:没有历史,直接 GET /config 的 model + agent='build'。
7111
7497
  * 幂等:同一 instructionId 在本进程里只建一次(跨重启靠 daemon 的 seen 去重);/session 不收客户端指定 id,所以不做确定性 id
7112
7498
  */
7113
- async startTask(cwd, text, instructionId) {
7499
+ async startTask(cwd, text, instructionId, expireAt = 0) {
7114
7500
  const notSent = (reason, message) => ({ status: "not_sent", reason, message });
7115
7501
  if (this.#d.enabled && !this.#d.enabled()) return notSent("tool_unsupported", "\u767E\u5EA6\u642D\u5B50\u6CA1\u6709\u63A5\u5165");
7116
7502
  const existing = instructionId ? this.#newTasks.get(instructionId) : void 0;
7117
7503
  if (existing) return { status: "started", sessionId: existing };
7118
- const got = await this.#ensureTarget();
7504
+ const got = await this.#ensureTarget(expireAt);
7119
7505
  if (typeof got === "string") return notSent(got, unavailableMessage(got));
7120
7506
  const t = got;
7121
7507
  const http = this.#http(t);
@@ -7149,10 +7535,10 @@ var DumateWatch = class {
7149
7535
  * 请求体与界面自己发消息一致:带这个会话最后一条用户消息的 agent / model(没有就 build + /config 的 model)。
7150
7536
  * 先查 /session/status 判忙闲(busy / preparing / retry → queued),再 POST prompt_async(带会话目录)
7151
7537
  */
7152
- async deliverReply(sessionId, text) {
7538
+ async deliverReply(sessionId, text, expireAt = 0) {
7153
7539
  const notSent = (reason, message) => ({ status: "not_sent", reason, message });
7154
7540
  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();
7541
+ const got = await this.#ensureTarget(expireAt);
7156
7542
  if (typeof got === "string") return notSent(got, unavailableMessage(got));
7157
7543
  const t = got;
7158
7544
  const http = this.#http(t);
@@ -7567,7 +7953,7 @@ var DumateWatch = class {
7567
7953
  const now = this.#now();
7568
7954
  if (now - this.#scanAt >= SCAN_DIRS_MS) {
7569
7955
  this.#scanAt = now;
7570
- for (const d of (this.#d.sessionDirs ?? (() => scanSessionDirs(join22(qianfanHome(), "workspace", "sessions"), now)))()) {
7956
+ for (const d of (this.#d.sessionDirs ?? (() => scanSessionDirs(join23(qianfanHome(), "workspace", "sessions"), now)))()) {
7571
7957
  if (!this.#dirs.has(dirKey(d))) this.#touchDir(d, now);
7572
7958
  }
7573
7959
  }
@@ -7679,9 +8065,9 @@ function runHook(node, input, agentPid, tool = "dumate") {
7679
8065
  }
7680
8066
 
7681
8067
  // 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";
8068
+ import { existsSync as existsSync20, mkdirSync as mkdirSync22, readdirSync as readdirSync12, readFileSync as readFileSync24, renameSync as renameSync16, writeFileSync as writeFileSync22 } from "fs";
7683
8069
  import { homedir as homedir7 } from "os";
7684
- import { join as join23, win32 as win325 } from "path";
8070
+ import { join as join24, win32 as win327 } from "path";
7685
8071
  var WAKE_LAUNCH_GAP_MS2 = 60 * 1e3;
7686
8072
  var RESTART_GAP_MS2 = 30 * 60 * 1e3;
7687
8073
  var RESTART_DAILY_MAX2 = 3;
@@ -7693,7 +8079,7 @@ function winBrandMainProc(procs, exe) {
7693
8079
  for (const p of procs) {
7694
8080
  const argv = winSplitArgs(p.cmdline);
7695
8081
  const first = argv[0] ?? "";
7696
- if (win325.basename(first).toLowerCase() !== want) continue;
8082
+ if (win327.basename(first).toLowerCase() !== want) continue;
7697
8083
  const rest = argv.slice(1);
7698
8084
  if (rest.some((a) => a.startsWith("--type=") || !a.startsWith("-") || /\.(c|m)?js$/i.test(a))) continue;
7699
8085
  return { pid: p.pid, exe: first };
@@ -7709,7 +8095,7 @@ function winBrandMains(procs, exe) {
7709
8095
  return out;
7710
8096
  }
7711
8097
  function isWinAppExe(p, exe) {
7712
- return typeof p === "string" && /^[a-z]:[\\/]/i.test(p) && win325.basename(p).toLowerCase() === exe.toLowerCase();
8098
+ return typeof p === "string" && /^[a-z]:[\\/]/i.test(p) && win327.basename(p).toLowerCase() === exe.toLowerCase();
7713
8099
  }
7714
8100
  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
8101
  function parseWinSession(out) {
@@ -7740,11 +8126,11 @@ function parseLsappBundle(out) {
7740
8126
  return m ? m[1] : null;
7741
8127
  }
7742
8128
  function toolBusy(tool, now = Date.now(), dir2 = stateDir(), alive = isAlive) {
7743
- if (!existsSync19(dir2)) return false;
7744
- for (const f of readdirSync11(dir2)) {
8129
+ if (!existsSync20(dir2)) return false;
8130
+ for (const f of readdirSync12(dir2)) {
7745
8131
  if (!f.endsWith(".json")) continue;
7746
8132
  try {
7747
- const s = JSON.parse(readFileSync23(join23(dir2, f), "utf8"));
8133
+ const s = JSON.parse(readFileSync24(join24(dir2, f), "utf8"));
7748
8134
  if (s.tool !== tool || s.state !== "watch" && s.state !== "ask_approval") continue;
7749
8135
  if (typeof s.pid === "number" && s.pid > 0) {
7750
8136
  if (alive(s.pid, s.pidName)) return true;
@@ -7757,7 +8143,7 @@ function toolBusy(tool, now = Date.now(), dir2 = stateDir(), alive = isAlive) {
7757
8143
  return false;
7758
8144
  }
7759
8145
  function defaultThrottleFile() {
7760
- return join23(deskbuddyHome(), "qoderapp", "keeper.json");
8146
+ return join24(deskbuddyHome(), "qoderapp", "keeper.json");
7761
8147
  }
7762
8148
  var QoderCdpKeeper = class {
7763
8149
  #d;
@@ -7847,7 +8233,7 @@ var QoderCdpKeeper = class {
7847
8233
  /** 节流状态(按品牌一份,落盘;读不出当没有) */
7848
8234
  #loadThrottle() {
7849
8235
  try {
7850
- const all = JSON.parse(readFileSync23(this.#file(), "utf8"));
8236
+ const all = JSON.parse(readFileSync24(this.#file(), "utf8"));
7851
8237
  const t = all?.[this.#d.brand.tool] ?? {};
7852
8238
  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
8239
  if (this.#d.brand.win && isWinAppExe(t.appExe, this.#d.brand.win.exe)) out.appExe = t.appExe;
@@ -7861,14 +8247,14 @@ var QoderCdpKeeper = class {
7861
8247
  try {
7862
8248
  let all = {};
7863
8249
  try {
7864
- all = JSON.parse(readFileSync23(f, "utf8"));
8250
+ all = JSON.parse(readFileSync24(f, "utf8"));
7865
8251
  } catch {
7866
8252
  all = {};
7867
8253
  }
7868
8254
  all[this.#d.brand.tool] = t;
7869
- mkdirSync21(join23(f, ".."), { recursive: true });
7870
- writeFileSync21(f + ".tmp", JSON.stringify(all));
7871
- renameSync15(f + ".tmp", f);
8255
+ mkdirSync22(join24(f, ".."), { recursive: true });
8256
+ writeFileSync22(f + ".tmp", JSON.stringify(all));
8257
+ renameSync16(f + ".tmp", f);
7872
8258
  } catch {
7873
8259
  }
7874
8260
  }
@@ -7941,14 +8327,15 @@ var QoderCdpKeeper = class {
7941
8327
  if (this.#d.appPath) return this.#d.appPath();
7942
8328
  if (this.#isWin()) {
7943
8329
  const win = this.#d.brand.win;
7944
- const exists = this.#d.exists ?? existsSync19;
8330
+ const exists = this.#d.exists ?? existsSync20;
7945
8331
  const t = this.#loadThrottle();
7946
- for (const p of [t.appExe, ...win.installPaths?.() ?? []]) if (isWinAppExe(p, win.exe) && exists(p)) return p;
8332
+ const located = this.#d.locatedExe ? this.#d.locatedExe() : cachedWinAppExe(this.#d.brand.tool);
8333
+ for (const p of [t.appExe, located, ...win.installPaths?.() ?? []]) if (isWinAppExe(p, win.exe) && exists(p)) return p;
7947
8334
  return null;
7948
8335
  }
7949
8336
  if (this.#platform() !== "darwin") return null;
7950
8337
  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;
8338
+ for (const p of [this.#d.brand.macApp, join24(homedir7(), "Applications", name)]) if (existsSync20(p)) return p;
7952
8339
  return null;
7953
8340
  }
7954
8341
  /** 一轮:健康检查 → 活着核对属主。🔴 平时不重启 App;只有端口无响应满 HUNG_RESTART_MS 时按节流与在场规则重启 */
@@ -8215,33 +8602,33 @@ var DUMATE_BRAND = {
8215
8602
  installPaths: () => {
8216
8603
  const out = [];
8217
8604
  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"));
8605
+ if (la) out.push(join25(la, "Programs", "DuMate", "DuMate.exe"), join25(la, "Programs", "qianfan-desktop-app", "DuMate.exe"));
8606
+ for (const pf of [process.env.ProgramFiles, process.env["ProgramFiles(x86)"]]) if (pf) out.push(join25(pf, "DuMate", "DuMate.exe"));
8220
8607
  return out;
8221
8608
  }
8222
8609
  }
8223
8610
  };
8224
8611
  var DM_DOWN_ROUNDS = 2;
8225
8612
  var DM_ACTIVE_MS = 10 * 60 * 1e3;
8226
- function dumateActivity(now = Date.now(), dataHome = dumateDataHome(), sessionsRoot = join24(qianfanHome(), "workspace", "sessions")) {
8613
+ function dumateActivity(now = Date.now(), dataHome = dumateDataHome(), sessionsRoot = join25(qianfanHome(), "workspace", "sessions")) {
8227
8614
  let latest = -1;
8228
- const xdg = join24(dataHome, "qianfan_desk_xdg");
8615
+ const xdg = join25(dataHome, "qianfan_desk_xdg");
8229
8616
  let users = [];
8230
8617
  try {
8231
- users = readdirSync12(xdg);
8618
+ users = readdirSync13(xdg);
8232
8619
  } catch {
8233
8620
  users = [];
8234
8621
  }
8235
8622
  let found = false;
8236
8623
  for (const u of users) {
8237
- const dir2 = join24(xdg, u, "data", "opencode");
8624
+ const dir2 = join25(xdg, u, "data", "opencode");
8238
8625
  try {
8239
- latest = Math.max(latest, statSync10(join24(dir2, "opencode.db")).mtimeMs);
8626
+ latest = Math.max(latest, statSync12(join25(dir2, "opencode.db")).mtimeMs);
8240
8627
  found = true;
8241
8628
  } catch {
8242
8629
  }
8243
8630
  try {
8244
- const w = statSync10(join24(dir2, "opencode.db-wal"));
8631
+ const w = statSync12(join25(dir2, "opencode.db-wal"));
8245
8632
  if (w.size > 0) latest = Math.max(latest, w.mtimeMs);
8246
8633
  } catch {
8247
8634
  }
@@ -8252,7 +8639,7 @@ function dumateActivity(now = Date.now(), dataHome = dumateDataHome(), sessionsR
8252
8639
  return "idle";
8253
8640
  }
8254
8641
  function dmKeeperThrottleFile() {
8255
- return join24(deskbuddyHome(), "dmapp", "keeper.json");
8642
+ return join25(deskbuddyHome(), "dmapp", "keeper.json");
8256
8643
  }
8257
8644
  var DumateKeeper = class {
8258
8645
  #d;
@@ -8362,7 +8749,8 @@ var DUMATE_SHORTCUT_APP = {
8362
8749
  port: dmPort,
8363
8750
  wanted: shortcutsWanted,
8364
8751
  off: () => loadConfig().dumateShortcuts === false,
8365
- skipEnv: "DESKBUDDY_SKIP_DM_LNK"
8752
+ skipEnv: "DESKBUDDY_SKIP_DM_LNK",
8753
+ locatedExe: () => cachedWinAppExe("dumate")
8366
8754
  };
8367
8755
  function ensureDumateShortcuts(d, ps = defaultPs) {
8368
8756
  return ensureAppShortcuts(DUMATE_SHORTCUT_APP, d, ps);
@@ -8433,7 +8821,8 @@ function qoderShortcutApp(tool) {
8433
8821
  port: () => portOf2(tool),
8434
8822
  wanted: () => qoderShortcutsWanted(tool),
8435
8823
  off: () => loadConfig().qoderShortcuts === false,
8436
- skipEnv: "DESKBUDDY_SKIP_QODER_LNK"
8824
+ skipEnv: "DESKBUDDY_SKIP_QODER_LNK",
8825
+ locatedExe: () => cachedWinAppExe(tool)
8437
8826
  };
8438
8827
  }
8439
8828
  function ensureQoderShortcuts(tool, d, ps = defaultPs) {
@@ -8447,15 +8836,15 @@ function restoreQoderShortcutsSync(tool, d, ps = defaultPsSync) {
8447
8836
  }
8448
8837
 
8449
8838
  // 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";
8839
+ import { chmodSync as chmodSync5, existsSync as existsSync21, mkdirSync as mkdirSync23, readFileSync as readFileSync25, realpathSync as realpathSync5, renameSync as renameSync17, rmSync as rmSync13, statSync as statSync13, writeFileSync as writeFileSync23 } from "fs";
8840
+ import { dirname as dirname13, join as join27 } from "path";
8452
8841
  var TRAE_EVENTS = ["UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop", "Notification"];
8453
8842
  var TRAE_NOTICE_TYPES = ["permission_prompt", "ask_user_question", "document_review", "browser_interaction"];
8454
8843
  var TRAE_NOTICE_MATCHER = `^(${TRAE_NOTICE_TYPES.join("|")})$`;
8455
8844
  var TRAE_NOTICE_TIMEOUT_S = 7 * 24 * 60 * 60;
8456
8845
  function readHooksFile(path) {
8457
- if (!existsSync20(path)) return {};
8458
- const raw = readFileSync24(path, "utf8");
8846
+ if (!existsSync21(path)) return {};
8847
+ const raw = readFileSync25(path, "utf8");
8459
8848
  if (raw.trim() === "") return {};
8460
8849
  let parsed;
8461
8850
  try {
@@ -8477,19 +8866,19 @@ function writeTarget(path) {
8477
8866
  }
8478
8867
  }
8479
8868
  function writeHooksFile(linkPath, f) {
8480
- mkdirSync22(dirname13(linkPath), { recursive: true });
8869
+ mkdirSync23(dirname13(linkPath), { recursive: true });
8481
8870
  const path = writeTarget(linkPath);
8482
8871
  let mode;
8483
8872
  try {
8484
- mode = statSync11(path).mode & 511;
8873
+ mode = statSync13(path).mode & 511;
8485
8874
  } catch {
8486
8875
  mode = void 0;
8487
8876
  }
8488
8877
  const tmp = path + ".deskbuddy.tmp";
8489
8878
  try {
8490
- writeFileSync22(tmp, JSON.stringify(f, null, 2) + "\n", "utf8");
8879
+ writeFileSync23(tmp, JSON.stringify(f, null, 2) + "\n", "utf8");
8491
8880
  if (mode !== void 0) chmodSync5(tmp, mode);
8492
- renameSync16(tmp, path);
8881
+ renameSync17(tmp, path);
8493
8882
  } catch (e) {
8494
8883
  try {
8495
8884
  rmSync13(tmp, { force: true });
@@ -8536,7 +8925,7 @@ function strip(f, scripts) {
8536
8925
  }
8537
8926
  function installTraeHook(path, hookPath, events = TRAE_EVENTS, platform = process.platform) {
8538
8927
  const f = readHooksFile(path);
8539
- const gatePath = join26(dirname13(hookPath), "gate.cjs");
8928
+ const gatePath = join27(dirname13(hookPath), "gate.cjs");
8540
8929
  const before = JSON.stringify(f);
8541
8930
  strip(f, [hookPath, gatePath]);
8542
8931
  if (f.version === void 0) f.version = 1;
@@ -8553,13 +8942,13 @@ function installTraeHook(path, hookPath, events = TRAE_EVENTS, platform = proces
8553
8942
  if (JSON.stringify(f) !== before) writeHooksFile(path, f);
8554
8943
  }
8555
8944
  function uninstallTraeHook(path, hookPath) {
8556
- if (!existsSync20(path)) return;
8945
+ if (!existsSync21(path)) return;
8557
8946
  const f = readHooksFile(path);
8558
- if (strip(f, [hookPath, join26(dirname13(hookPath), "gate.cjs")])) writeHooksFile(path, f);
8947
+ if (strip(f, [hookPath, join27(dirname13(hookPath), "gate.cjs")])) writeHooksFile(path, f);
8559
8948
  }
8560
8949
  function isTraeHookInstalled(path, hookPath) {
8561
8950
  try {
8562
- if (!existsSync20(path)) return false;
8951
+ if (!existsSync21(path)) return false;
8563
8952
  const f = readHooksFile(path);
8564
8953
  return Object.values(f.hooks ?? {}).some((groups) => (groups ?? []).some((g) => (g?.hooks ?? []).some((h) => mentions2(h?.command, hookPath))));
8565
8954
  } catch {
@@ -8569,19 +8958,19 @@ function isTraeHookInstalled(path, hookPath) {
8569
8958
  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
8959
 
8571
8960
  // src/commands/tools.ts
8572
- import { existsSync as existsSync22, readFileSync as readFileSync26 } from "fs";
8961
+ import { existsSync as existsSync23, readFileSync as readFileSync27 } from "fs";
8573
8962
 
8574
8963
  // 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";
8964
+ import { chmodSync as chmodSync6, existsSync as existsSync22, lstatSync as lstatSync2, mkdirSync as mkdirSync24, readFileSync as readFileSync26, realpathSync as realpathSync6, renameSync as renameSync18, statSync as statSync14, writeFileSync as writeFileSync24 } from "fs";
8965
+ import { dirname as dirname14, join as join28 } from "path";
8577
8966
  var CB_MCP_NAME = "deskbuddy";
8578
8967
  var CB_CHANNEL_SCRIPT = "cbchannel.cjs";
8579
8968
  function cbChannelScript(hookPath) {
8580
- return join27(dirname14(hookPath), CB_CHANNEL_SCRIPT);
8969
+ return join28(dirname14(hookPath), CB_CHANNEL_SCRIPT);
8581
8970
  }
8582
8971
  function read(path) {
8583
- if (!existsSync21(path)) return {};
8584
- const raw = readFileSync25(path, "utf8");
8972
+ if (!existsSync22(path)) return {};
8973
+ const raw = readFileSync26(path, "utf8");
8585
8974
  if (raw.trim() === "") return {};
8586
8975
  let v;
8587
8976
  try {
@@ -8599,17 +8988,17 @@ function write2(file3, v) {
8599
8988
  } catch {
8600
8989
  path = file3;
8601
8990
  }
8602
- mkdirSync23(dirname14(path), { recursive: true });
8991
+ mkdirSync24(dirname14(path), { recursive: true });
8603
8992
  let mode;
8604
8993
  try {
8605
- mode = statSync12(path).mode & 511;
8994
+ mode = statSync14(path).mode & 511;
8606
8995
  } catch {
8607
8996
  mode = void 0;
8608
8997
  }
8609
8998
  const tmp = path + ".deskbuddy.tmp";
8610
- writeFileSync23(tmp, JSON.stringify(v, null, 2), "utf8");
8999
+ writeFileSync24(tmp, JSON.stringify(v, null, 2), "utf8");
8611
9000
  if (mode !== void 0) chmodSync6(tmp, mode);
8612
- renameSync17(tmp, path);
9001
+ renameSync18(tmp, path);
8613
9002
  }
8614
9003
  function ours(entry) {
8615
9004
  if (!entry || typeof entry !== "object") return false;
@@ -8628,7 +9017,7 @@ function installCbChannel(mcpPath, scriptPath, nodePath = CB_NODE) {
8628
9017
  write2(mcpPath, f);
8629
9018
  }
8630
9019
  function uninstallCbChannel(mcpPath) {
8631
- if (!existsSync21(mcpPath)) return;
9020
+ if (!existsSync22(mcpPath)) return;
8632
9021
  const f = read(mcpPath);
8633
9022
  const servers = f.mcpServers;
8634
9023
  if (!servers || typeof servers !== "object" || Array.isArray(servers) || !ours(servers[CB_MCP_NAME])) return;
@@ -8649,8 +9038,8 @@ function codebuddyWanted(d = { standalone: () => standaloneProbe() }) {
8649
9038
  }
8650
9039
  function fileContains(path, needle) {
8651
9040
  try {
8652
- if (!existsSync22(path)) return false;
8653
- return readFileSync26(path, "utf8").includes(needle);
9041
+ if (!existsSync23(path)) return false;
9042
+ return readFileSync27(path, "utf8").includes(needle);
8654
9043
  } catch {
8655
9044
  return false;
8656
9045
  }
@@ -8751,7 +9140,7 @@ ADAPTERS["dumate-http"] = {
8751
9140
  uninstall: () => restoreDumateShortcutsSync({ log: dmLnkLog }),
8752
9141
  isInstalled: (p) => {
8753
9142
  try {
8754
- const enabled = JSON.parse(readFileSync26(p, "utf8")).enabledTools;
9143
+ const enabled = JSON.parse(readFileSync27(p, "utf8")).enabledTools;
8755
9144
  return Array.isArray(enabled) && enabled.includes("dumate");
8756
9145
  } catch {
8757
9146
  return false;
@@ -8772,7 +9161,7 @@ ADAPTERS["trae-hooks"] = {
8772
9161
  extraConfigPaths: (tool, purpose) => {
8773
9162
  if (tool !== "trae") return [];
8774
9163
  const primary = pathResolve(traeHooksPath());
8775
- return traeHooksPaths().filter((p) => pathResolve(p) !== primary && (purpose === "uninstall" || existsSync22(pathDirname(p))));
9164
+ return traeHooksPaths().filter((p) => pathResolve(p) !== primary && (purpose === "uninstall" || existsSync23(pathDirname(p))));
8776
9165
  }
8777
9166
  };
8778
9167
  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 +9173,7 @@ ADAPTERS["coze-cdp"] = {
8784
9173
  },
8785
9174
  isInstalled: (p) => {
8786
9175
  try {
8787
- const enabled = JSON.parse(readFileSync26(p, "utf8")).enabledTools;
9176
+ const enabled = JSON.parse(readFileSync27(p, "utf8")).enabledTools;
8788
9177
  return Array.isArray(enabled) && enabled.includes("coze");
8789
9178
  } catch {
8790
9179
  return false;
@@ -8815,9 +9204,9 @@ function subscribedEvents(registry, tool, fallback) {
8815
9204
  }
8816
9205
 
8817
9206
  // src/wrap/pty.ts
8818
- import { chmodSync as chmodSync7, existsSync as existsSync23, statSync as statSync13 } from "fs";
9207
+ import { chmodSync as chmodSync7, existsSync as existsSync24, statSync as statSync15 } from "fs";
8819
9208
  import { createRequire as createRequire4 } from "module";
8820
- import { dirname as dirname15, join as join28 } from "path";
9209
+ import { dirname as dirname15, join as join29 } from "path";
8821
9210
  import { fileURLToPath as fileURLToPath4 } from "url";
8822
9211
  var req = createRequire4(import.meta.url);
8823
9212
  var VENDORED_PTY_PLATFORMS = ["darwin-arm64", "darwin-x64", "win32-arm64", "win32-x64"];
@@ -8827,8 +9216,8 @@ function hasVendoredPty(platform = process.platform, arch = process.arch) {
8827
9216
  function vendoredPtyDir(from = dirname15(fileURLToPath4(import.meta.url))) {
8828
9217
  let dir2 = from;
8829
9218
  for (let i = 0; i < 4; i++) {
8830
- const cand = join28(dir2, "vendor", "node-pty");
8831
- if (existsSync23(join28(cand, "package.json"))) return cand;
9219
+ const cand = join29(dir2, "vendor", "node-pty");
9220
+ if (existsSync24(join29(cand, "package.json"))) return cand;
8832
9221
  const up = dirname15(dir2);
8833
9222
  if (up === dir2) break;
8834
9223
  dir2 = up;
@@ -8838,8 +9227,8 @@ function vendoredPtyDir(from = dirname15(fileURLToPath4(import.meta.url))) {
8838
9227
  function fixSpawnHelper(ptyDir) {
8839
9228
  if (process.platform !== "darwin") return;
8840
9229
  try {
8841
- const helper = join28(ptyDir, "prebuilds", `darwin-${process.arch}`, "spawn-helper");
8842
- if (existsSync23(helper) && (statSync13(helper).mode & 73) === 0) chmodSync7(helper, 493);
9230
+ const helper = join29(ptyDir, "prebuilds", `darwin-${process.arch}`, "spawn-helper");
9231
+ if (existsSync24(helper) && (statSync15(helper).mode & 73) === 0) chmodSync7(helper, 493);
8843
9232
  } catch {
8844
9233
  }
8845
9234
  }
@@ -8943,10 +9332,10 @@ function accentLine(term, tabRow = /←.*[☐☒].*Submit.*→/) {
8943
9332
  }
8944
9333
 
8945
9334
  // src/commands/migrateQoder.ts
8946
- import { dirname as dirname17, join as join29 } from "path";
9335
+ import { dirname as dirname17, join as join30 } from "path";
8947
9336
 
8948
9337
  // src/core/detect.ts
8949
- import { existsSync as existsSync24 } from "fs";
9338
+ import { existsSync as existsSync25 } from "fs";
8950
9339
  import { dirname as dirname16 } from "path";
8951
9340
 
8952
9341
  // src/daemon/cozeApp/brand.ts
@@ -9026,7 +9415,7 @@ function toolDetectDirs(tool) {
9026
9415
  }
9027
9416
  var defaultDetectDeps = () => ({
9028
9417
  located: (tool) => locate(tool) != null,
9029
- exists: existsSync24,
9418
+ exists: existsSync25,
9030
9419
  env: process.env
9031
9420
  });
9032
9421
  function callerTool(env) {
@@ -9047,7 +9436,7 @@ function migrateLegacyQoder(out, detect = () => detectTools()) {
9047
9436
  try {
9048
9437
  const cfg = loadConfig();
9049
9438
  const hookPath = installedHookPath();
9050
- const scripts = [hookPath, join29(dirname17(hookPath), "gate.cjs")];
9439
+ const scripts = [hookPath, join30(dirname17(hookPath), "gate.cjs")];
9051
9440
  if (!retagCcHook(qoderCnSettingsPath(), scripts, "qoder", "qoder_cn")) return false;
9052
9441
  const hadQoder = cfg.enabledTools.includes("qoder");
9053
9442
  let intl = false;
@@ -9076,26 +9465,26 @@ function migrateLegacyQoder(out, detect = () => detectTools()) {
9076
9465
 
9077
9466
  // src/daemon/loginGuide.ts
9078
9467
  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";
9468
+ import { chmodSync as chmodSync9, existsSync as existsSync26, mkdirSync as mkdirSync26, readFileSync as readFileSync28, writeFileSync as writeFileSync26 } from "fs";
9080
9469
  import { homedir as homedir9 } from "os";
9081
- import { join as join33 } from "path";
9470
+ import { join as join34 } from "path";
9082
9471
 
9083
9472
  // src/daemon/executor.ts
9084
9473
  import { execFile as execFile7, execFileSync as execFileSync11 } from "child_process";
9085
9474
  import { homedir as homedir8 } from "os";
9086
- import { join as join31 } from "path";
9475
+ import { join as join32 } from "path";
9087
9476
 
9088
9477
  // src/core/codebuddyDirs.ts
9089
- import { join as join30, win32 as win326, posix as posix3 } from "path";
9478
+ import { join as join31, win32 as win328, posix as posix3 } from "path";
9090
9479
  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);
9480
+ const pa = platform === "win32" ? win328.resolve(a).toLowerCase() : posix3.resolve(a);
9481
+ const pb = platform === "win32" ? win328.resolve(b).toLowerCase() : posix3.resolve(b);
9093
9482
  return pa === pb;
9094
9483
  }
9095
9484
  function workbuddyRunConfigDir(stateDir2, home, platform = process.platform) {
9096
- const wb = join30(home, ".workbuddy");
9485
+ const wb = join31(home, ".workbuddy");
9097
9486
  if (typeof stateDir2 !== "string" || !stateDir2) return wb;
9098
- const cb = join30(home, ".codebuddy");
9487
+ const cb = join31(home, ".codebuddy");
9099
9488
  if (same(stateDir2, cb, platform)) return cb;
9100
9489
  return wb;
9101
9490
  }
@@ -9187,7 +9576,7 @@ function buildSpawnPlan(cmd, cwd, gate, home = homedir8(), configDir, platform =
9187
9576
  file: file3,
9188
9577
  args: workbuddyPlan(cmd, gate),
9189
9578
  cwd,
9190
- env: { CODEBUDDY_CONFIG_DIR: cmd.sessionId ? workbuddyRunConfigDir(configDir, home, platform) : join31(home, ".workbuddy") }
9579
+ env: { CODEBUDDY_CONFIG_DIR: cmd.sessionId ? workbuddyRunConfigDir(configDir, home, platform) : join32(home, ".workbuddy") }
9191
9580
  };
9192
9581
  case "qoder_cn":
9193
9582
  case "qoder":
@@ -9455,9 +9844,9 @@ function killTree(child) {
9455
9844
 
9456
9845
  // src/daemon/openWindow.ts
9457
9846
  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";
9847
+ import { chmodSync as chmodSync8, mkdirSync as mkdirSync25, rmSync as rmSync14, writeFileSync as writeFileSync25 } from "fs";
9459
9848
  import { userInfo } from "os";
9460
- import { join as join32 } from "path";
9849
+ import { join as join33 } from "path";
9461
9850
  function safePath(p) {
9462
9851
  return /^(?:[A-Za-z]:[\\/]|\/)/.test(p) && !/[\r\n\0"'`$%;\u2018-\u201b]/.test(p);
9463
9852
  }
@@ -9556,9 +9945,9 @@ var defaultOpenerDeps = (findWt2) => ({
9556
9945
  async function openTerminal(t, d) {
9557
9946
  checkTarget(t);
9558
9947
  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 });
9948
+ mkdirSync25(launchDir(t.home), { recursive: true, mode: 448 });
9949
+ const file3 = join33(launchDir(t.home), `${t.runId}.command`);
9950
+ writeFileSync25(file3, macCommandScript(t), { encoding: "utf8", mode: 448 });
9562
9951
  chmodSync8(file3, 448);
9563
9952
  await d.run("open", ["-a", "Terminal", file3]);
9564
9953
  return "terminal";
@@ -9577,7 +9966,7 @@ async function openTerminal(t, d) {
9577
9966
  function removeCommandFile(home, runId) {
9578
9967
  if (!RUN_ID_RE.test(runId)) return;
9579
9968
  try {
9580
- rmSync14(join32(launchDir(home), `${runId}.command`), { force: true });
9969
+ rmSync14(join33(launchDir(home), `${runId}.command`), { force: true });
9581
9970
  } catch {
9582
9971
  }
9583
9972
  }
@@ -9659,8 +10048,8 @@ var WORKBUDDY_PROBE_ARGS = ["-p", PROBE_TEXT, "--output-format", "json", "--no-s
9659
10048
  var WORKBUDDY_AUTH_FILE = "Tencent-Cloud.coding-copilot.info";
9660
10049
  function workbuddyAuthFile(home, platform, localAppData = "") {
9661
10050
  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;
10051
+ if (platform === "darwin") return join34(home, "Library", "Application Support", ...tail);
10052
+ if (platform === "win32") return localAppData ? join34(localAppData, ...tail) : null;
9664
10053
  return null;
9665
10054
  }
9666
10055
  var WORKBUDDY_VIA_APP_NOTE = "WorkBuddy \u5C06\u901A\u8FC7\u684C\u9762 App \u8FD0\u884C\uFF0C\u5728 App \u91CC\u767B\u5F55\u5373\u53EF";
@@ -9674,7 +10063,7 @@ async function checkWorkbuddy(d) {
9674
10063
  const auth = d.workbuddyAuthFile?.() ?? null;
9675
10064
  if (auth && d.exists?.(auth)) return { tool, state: "logged_in", note: "\u72EC\u7ACB\u7248\u51ED\u636E\u6587\u4EF6\u5728" };
9676
10065
  }
9677
- const env = envFor(d, loc, { CODEBUDDY_CONFIG_DIR: join33(d.home, ".workbuddy"), DESKBUDDY_NO_WAKE: "1", [PROBE_ENV]: "1" });
10066
+ const env = envFor(d, loc, { CODEBUDDY_CONFIG_DIR: join34(d.home, ".workbuddy"), DESKBUDDY_NO_WAKE: "1", [PROBE_ENV]: "1" });
9678
10067
  const r = await d.run(loc.file, [...loc.prefixArgs, ...WORKBUDDY_PROBE_ARGS], env, WORKBUDDY_PROBE_TIMEOUT_MS);
9679
10068
  const bad = failedRun(r);
9680
10069
  if (bad) return { tool, state: "unknown", note: bad };
@@ -9759,7 +10148,7 @@ var defaultCheckDeps = () => ({
9759
10148
  home: homedir9(),
9760
10149
  codexConfig: () => {
9761
10150
  try {
9762
- return readFileSync27(join33(codexHome(), "config.toml"), "utf8");
10151
+ return readFileSync28(join34(codexHome(), "config.toml"), "utf8");
9763
10152
  } catch {
9764
10153
  return null;
9765
10154
  }
@@ -9769,9 +10158,9 @@ var defaultCheckDeps = () => ({
9769
10158
  // 与设备任务唤起 App 同一个 locate(locateWorkbuddyAppExe:注册表卸载项 / App Paths / 开始菜单快捷方式…);App 自带 CLI 找得到也算
9770
10159
  workbuddyAppInstalled: () => locateWorkbuddyAppExe() !== null || locateWorkbuddyApp() !== null,
9771
10160
  workbuddyAuthFile: () => workbuddyAuthFile(homedir9(), process.platform, process.env.LOCALAPPDATA ?? ""),
9772
- exists: (p) => existsSync25(p)
10161
+ exists: (p) => existsSync26(p)
9773
10162
  });
9774
- function daemonCheckDeps(base = defaultLocateDeps(), exeHintsAsync = winWorkbuddyExeHintsAsync) {
10163
+ function daemonCheckDeps(base = defaultLocateDeps(), exeHintsAsync = winWorkbuddyLocatorHints) {
9775
10164
  const noPs = { ...base, winAppHints: void 0, winAppExeHints: void 0 };
9776
10165
  return {
9777
10166
  ...defaultCheckDeps(),
@@ -9873,7 +10262,7 @@ function winLoginScript(cmd) {
9873
10262
  return lines.join("\r\n") + "\r\n";
9874
10263
  }
9875
10264
  function loginScriptPath(home, tool, platform) {
9876
- return join33(launchDir(home), `login-${tool}.${platform === "win32" ? "ps1" : "command"}`);
10265
+ return join34(launchDir(home), `login-${tool}.${platform === "win32" ? "ps1" : "command"}`);
9877
10266
  }
9878
10267
  function winLoginWtArgs(script) {
9879
10268
  if (!safePath(script)) throw new Error("\u811A\u672C\u8DEF\u5F84\u91CC\u6709\u4E0D\u80FD\u8FDB\u547D\u4EE4\u884C\u7684\u5B57\u7B26");
@@ -9910,14 +10299,14 @@ var defaultLoginOpenDeps = () => ({
9910
10299
  home: deskbuddyHome(),
9911
10300
  pathDirs: () => childPath().split(":"),
9912
10301
  write: (file3, content) => {
9913
- mkdirSync25(join33(file3, ".."), { recursive: true, mode: 448 });
9914
- writeFileSync25(file3, content, { mode: 448 });
10302
+ mkdirSync26(join34(file3, ".."), { recursive: true, mode: 448 });
10303
+ writeFileSync26(file3, content, { mode: 448 });
9915
10304
  chmodSync9(file3, 448);
9916
10305
  }
9917
10306
  });
9918
10307
  async function installCodebuddyHooks(log) {
9919
10308
  try {
9920
- const { installTools: installTools2 } = await import("./enable-KQYXVSQN.js");
10309
+ const { installTools: installTools2 } = await import("./enable-EDDFXBHM.js");
9921
10310
  installTools2(["workbuddy"], () => {
9922
10311
  }, null, { auto: true });
9923
10312
  } catch (e) {
@@ -10102,8 +10491,8 @@ async function autoEnableLoginLog(tools, check, log) {
10102
10491
 
10103
10492
  // src/daemon/workbuddySetup.ts
10104
10493
  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";
10494
+ import { existsSync as existsSync27 } from "fs";
10495
+ import { dirname as dirname18, join as join35 } from "path";
10107
10496
  var DIALOG_TITLE = "DeskBuddy";
10108
10497
  var BTN_INSTALL = "\u5B89\u88C5";
10109
10498
  var BTN_LATER = "\u6682\u4E0D";
@@ -10242,9 +10631,9 @@ function npmPlan(d) {
10242
10631
  for (const f of whichAllIn("npm", d)) {
10243
10632
  if (!/\.cmd$/i.test(f)) continue;
10244
10633
  const dir2 = dirname18(f);
10245
- const cli = join34(dir2, "node_modules", "npm", "bin", "npm-cli.js");
10634
+ const cli = join35(dir2, "node_modules", "npm", "bin", "npm-cli.js");
10246
10635
  if (!d.exists(cli)) continue;
10247
- const node = join34(dir2, "node.exe");
10636
+ const node = join35(dir2, "node.exe");
10248
10637
  return { file: d.exists(node) ? node : d.nodePath, args: [cli, ...NPM_INSTALL_ARGS] };
10249
10638
  }
10250
10639
  return null;
@@ -10361,7 +10750,7 @@ async function runOffer(source, d, opts = {}) {
10361
10750
  }
10362
10751
  }
10363
10752
  function lockPath() {
10364
- return join34(deskbuddyHome(), "workbuddy-setup.lock");
10753
+ return join35(deskbuddyHome(), "workbuddy-setup.lock");
10365
10754
  }
10366
10755
  var defaultLockDeps = () => ({ path: lockPath(), pid: process.pid, now: () => Date.now(), alive: pidAlive2 });
10367
10756
  var WorkbuddyOffer = class {
@@ -10431,7 +10820,7 @@ function defaultOfferDeps(log) {
10431
10820
  }),
10432
10821
  openLogin: () => defaultOpenLogin(platform),
10433
10822
  installHooks: async () => {
10434
- const { installTools: installTools2 } = await import("./enable-KQYXVSQN.js");
10823
+ const { installTools: installTools2 } = await import("./enable-EDDFXBHM.js");
10435
10824
  installTools2(["workbuddy"], (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`), null, { auto: true });
10436
10825
  },
10437
10826
  log: (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`),
@@ -10520,7 +10909,7 @@ function defaultPromptStartDeps(env = process.env) {
10520
10909
  gui: () => guiAvailable(defaultGuiDeps()),
10521
10910
  spawnChild: (source) => {
10522
10911
  const cli = process.argv[1];
10523
- if (!cli || !existsSync26(cli)) throw new Error("\u627E\u4E0D\u5230 deskbuddy \u81EA\u5DF1\u7684\u5165\u53E3\u6587\u4EF6");
10912
+ if (!cli || !existsSync27(cli)) throw new Error("\u627E\u4E0D\u5230 deskbuddy \u81EA\u5DF1\u7684\u5165\u53E3\u6587\u4EF6");
10524
10913
  const c = spawn5(process.execPath, [cli, "workbuddy-setup", "--from", source], {
10525
10914
  detached: true,
10526
10915
  stdio: "ignore",
@@ -10600,14 +10989,14 @@ function installTools(tools, out, shim = defaultShimEnvForInstall(), opts = {})
10600
10989
  installed.push(tool);
10601
10990
  out(`\u2705 ${tool}\uFF1Ahook \u5DF2\u88C5\u8FDB ${configPath2}\uFF08${events.length} \u4E2A\u4E8B\u4EF6\uFF09`);
10602
10991
  try {
10603
- if (adapter.installWindowGate?.(configPath2, join35(dirname19(hookPath), "gate.cjs"), tool)) {
10992
+ if (adapter.installWindowGate?.(configPath2, join36(dirname19(hookPath), "gate.cjs"), tool)) {
10604
10993
  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
10994
  }
10606
10995
  } catch (e) {
10607
10996
  out(`\u26A0\uFE0F ${tool}\uFF1A\u8BBE\u5907\u786E\u8BA4\u6388\u6743\u6CA1\u88C5\u4E0A\uFF1A${e.message}`);
10608
10997
  }
10609
10998
  try {
10610
- if (adapter.installRewake?.(configPath2, join35(dirname19(hookPath), "wake.cjs"), tool)) {
10999
+ if (adapter.installRewake?.(configPath2, join36(dirname19(hookPath), "wake.cjs"), tool)) {
10611
11000
  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
11001
  }
10613
11002
  } catch (e) {
@@ -10731,6 +11120,9 @@ export {
10731
11120
  accentLine,
10732
11121
  scrubAgentEnv,
10733
11122
  scrubForCommand,
11123
+ WIN_APP_SPECS,
11124
+ LOCATE_EVERY_MS,
11125
+ winAppLocator,
10734
11126
  defaultLocateDeps,
10735
11127
  PROBE_TIMEOUT_MS,
10736
11128
  PROBE_SHORT_TTL_MS,
@@ -10859,6 +11251,8 @@ export {
10859
11251
  cleanFeedback,
10860
11252
  parseApprovalFrame,
10861
11253
  cleanDeviceName,
11254
+ APP_WAIT_MARGIN_MS,
11255
+ appWaitBudget,
10862
11256
  isExpired,
10863
11257
  NOT_REGISTERED_RE_SRC,
10864
11258
  PROBE,