deskbuddy 0.2.0-beta.34 → 0.2.0-beta.35

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.
@@ -5608,10 +5608,13 @@ var defaultFetchJson = async (url, timeoutMs) => {
5608
5608
  clearTimeout(t);
5609
5609
  }
5610
5610
  };
5611
- function pickMainTarget(list) {
5612
- if (!Array.isArray(list)) return null;
5611
+ function mainTargets(list) {
5612
+ if (!Array.isArray(list)) return [];
5613
5613
  const pages = list.filter((t) => t && t.type === "page" && typeof t.webSocketDebuggerUrl === "string" && !/^devtools:/.test(t.url ?? ""));
5614
- return pages.find((t) => /renderer\/index\.html/.test(t.url ?? "")) ?? null;
5614
+ return pages.filter((t) => /renderer\/index\.html/.test(t.url ?? ""));
5615
+ }
5616
+ function pickMainTarget(list) {
5617
+ return mainTargets(list)[0] ?? null;
5615
5618
  }
5616
5619
  function loopbackWsUrl(wsUrl, port) {
5617
5620
  try {
@@ -5624,16 +5627,18 @@ function loopbackWsUrl(wsUrl, port) {
5624
5627
  return null;
5625
5628
  }
5626
5629
  }
5627
- async function connectCdp(port, d = {}) {
5628
- const fetchJson = d.fetchJson ?? defaultFetchJson;
5629
- const target = (d.pick ?? pickMainTarget)(await fetchJson(`http://127.0.0.1:${port}/json/list`, 3e3));
5630
- if (!target?.webSocketDebuggerUrl) throw new Error("\u627E\u4E0D\u5230 App \u4E3B\u7A97\u53E3");
5630
+ var HAS_SDK_EXPR = "!!(globalThis.wb && globalThis.wb.conversations)";
5631
+ function pageName(url) {
5632
+ return String(url || "").split(/[?#]/)[0].split(/[\\/]/).slice(-2).join("/");
5633
+ }
5634
+ function openWs(target, port, d, info) {
5635
+ if (!target.webSocketDebuggerUrl) return Promise.reject(new Error("\u627E\u4E0D\u5230 App \u4E3B\u7A97\u53E3"));
5631
5636
  const url = loopbackWsUrl(target.webSocketDebuggerUrl, port);
5632
- if (!url) throw new Error("\u4E3B\u7A97\u53E3\u7684\u8C03\u8BD5\u5730\u5740\u4E0D\u5BF9");
5637
+ if (!url) return Promise.reject(new Error("\u4E3B\u7A97\u53E3\u7684\u8C03\u8BD5\u5730\u5740\u4E0D\u5BF9"));
5633
5638
  const ws = (d.ws ?? ((u) => new WebSocket(u, { handshakeTimeout: 5e3 })))(url);
5634
- return await new Promise((resolve6, reject) => {
5639
+ return new Promise((resolve6, reject) => {
5635
5640
  let opened = false;
5636
- const client = new WsCdpClient(ws);
5641
+ const client = new WsCdpClient(ws, info);
5637
5642
  ws.on("open", () => {
5638
5643
  opened = true;
5639
5644
  resolve6(client);
@@ -5648,13 +5653,53 @@ async function connectCdp(port, d = {}) {
5648
5653
  ws.on("message", (data) => client.onMessage(data));
5649
5654
  });
5650
5655
  }
5656
+ async function connectCdp(port, d = {}) {
5657
+ const fetchJson = d.fetchJson ?? defaultFetchJson;
5658
+ const list = await fetchJson(`http://127.0.0.1:${port}/json/list`, 3e3);
5659
+ if (!d.pickSdk) {
5660
+ const target = (d.pick ?? pickMainTarget)(list);
5661
+ if (!target?.webSocketDebuggerUrl) throw new Error("\u627E\u4E0D\u5230 App \u4E3B\u7A97\u53E3");
5662
+ return openWs(target, port, d);
5663
+ }
5664
+ const all = mainTargets(list);
5665
+ if (all.length === 0) throw new Error("\u627E\u4E0D\u5230 App \u4E3B\u7A97\u53E3");
5666
+ let ua = "";
5667
+ try {
5668
+ const v = await fetchJson(`http://127.0.0.1:${port}/json/version`, 2e3);
5669
+ const m = typeof v?.["User-Agent"] === "string" ? /\bWorkBuddy\/[\w.-]+/.exec(v["User-Agent"]) : null;
5670
+ ua = [m?.[0], typeof v?.Browser === "string" ? v.Browser : ""].filter(Boolean).join(" ");
5671
+ } catch {
5672
+ ua = "";
5673
+ }
5674
+ const infoOf = (i, sdk) => ({ url: pageName(String(all[i].url ?? "")), title: "", candidates: all.length, index: i, sdk, ...ua ? { ua } : {} });
5675
+ if (all.length === 1) return openWs(all[0], port, d, infoOf(0, null));
5676
+ for (let i = 0; i < all.length; i++) {
5677
+ let c;
5678
+ try {
5679
+ c = await openWs(all[i], port, d, infoOf(i, true));
5680
+ } catch {
5681
+ continue;
5682
+ }
5683
+ let has = false;
5684
+ try {
5685
+ has = await c.evaluate(HAS_SDK_EXPR, 2e3) === true;
5686
+ } catch {
5687
+ has = false;
5688
+ }
5689
+ if (has) return c;
5690
+ c.close();
5691
+ }
5692
+ return openWs(all[0], port, d, infoOf(0, false));
5693
+ }
5651
5694
  var WsCdpClient = class {
5652
5695
  #ws;
5696
+ target;
5653
5697
  #id = 0;
5654
5698
  #closed = false;
5655
5699
  #waiting = /* @__PURE__ */ new Map();
5656
- constructor(ws) {
5700
+ constructor(ws, target) {
5657
5701
  this.#ws = ws;
5702
+ if (target) this.target = target;
5658
5703
  }
5659
5704
  get closed() {
5660
5705
  return this.#closed;
@@ -5858,6 +5903,7 @@ var HungTracker = class {
5858
5903
  }
5859
5904
  };
5860
5905
  var APP_PORT_HUNG_REASON = "app_port_unresponsive";
5906
+ var STARTUP_GRACE_MS = 90 * 1e3;
5861
5907
  var FORCE_QUIT_WAIT_MS = 10 * 1e3;
5862
5908
  function isTimeoutError(e) {
5863
5909
  const err = e;
@@ -6005,6 +6051,12 @@ var CdpKeeper = class {
6005
6051
  #hung = new HungTracker();
6006
6052
  /** 这一段卡死里「暂不重启」的原因记过哪条(同一条只记一次) */
6007
6053
  #hungNote = "";
6054
+ /** 启动宽限期到什么时候(0 = 不在宽限期) */
6055
+ #graceUntil = 0;
6056
+ /** 宽限期内超时记过一行没有(每段宽限只记一次) */
6057
+ #graceNoted = false;
6058
+ /** 上一轮端口检查的结果(判「刚从不通变成通」) */
6059
+ #lastPort = null;
6008
6060
  /** 这一段端口不通里「不重启」的原因记过哪条(同一条只记一次;端口通了 / 转成卡死时清掉。258:原先这些分支一行不记) */
6009
6061
  #downNote = "";
6010
6062
  /** 最近一轮端口不通的细分(日志 / 设备任务的等待原因用):fail = 没在监听或回包不是 WorkBuddy;timeout = 超时且监听者不是本机回环的 WorkBuddy */
@@ -6149,6 +6201,20 @@ var CdpKeeper = class {
6149
6201
  this.#hung.clear();
6150
6202
  this.#hungNote = "";
6151
6203
  }
6204
+ /** 进入启动宽限期(拉起 / 重启 / 端口刚从不通变成通) */
6205
+ #startGrace(now) {
6206
+ this.#graceUntil = now + STARTUP_GRACE_MS;
6207
+ this.#graceNoted = false;
6208
+ }
6209
+ #inGrace(now) {
6210
+ return this.#graceUntil > 0 && now < this.#graceUntil;
6211
+ }
6212
+ /** 宽限期内超时:记一行(每段一次) */
6213
+ #noteGrace() {
6214
+ if (this.#graceNoted) return;
6215
+ this.#graceNoted = true;
6216
+ this.#d.log(`WorkBuddy \u8C03\u8BD5\u7AEF\u53E3 ${this.#d.port()} \u8D85\u65F6\uFF0C\u4F46 App \u521A\u542F\u52A8 / \u521A\u6062\u590D\uFF08${STARTUP_GRACE_MS / 1e3} \u79D2\u5BBD\u9650\u671F\u5185\uFF09\uFF0C\u5148\u4E0D\u5224\u65E0\u54CD\u5E94`);
6217
+ }
6152
6218
  /** 最近一轮端口不通的细分(没不通过返回 '') */
6153
6219
  get downWhy() {
6154
6220
  return this.#downWhy;
@@ -6200,7 +6266,13 @@ var CdpKeeper = class {
6200
6266
  await this.ensureEnv().catch(() => {
6201
6267
  });
6202
6268
  const pr = await this.#probe();
6269
+ const st = await this.#tickInner(pr);
6270
+ this.#lastPort = st;
6271
+ return st;
6272
+ }
6273
+ async #tickInner(pr) {
6203
6274
  if (pr === "ok") {
6275
+ if (this.#lastPort === "down") this.#startGrace(this.#now());
6204
6276
  if (this.#hung.since) this.#d.log(`WorkBuddy \u8C03\u8BD5\u7AEF\u53E3 ${this.#d.port()} \u6062\u590D\u54CD\u5E94`);
6205
6277
  this.#markHung(false, 0);
6206
6278
  this.#downRounds = 0;
@@ -6211,6 +6283,11 @@ var CdpKeeper = class {
6211
6283
  });
6212
6284
  return ok ? "up" : "untrusted";
6213
6285
  }
6286
+ if (pr === "timeout" && this.#inGrace(this.#now()) && await this.ownerOk().catch(() => false)) {
6287
+ this.#noteGrace();
6288
+ this.#downRounds = 0;
6289
+ return this.#lastPort === "up" ? "up" : "down";
6290
+ }
6214
6291
  if (await this.#isHung(pr)) {
6215
6292
  const now = this.#now();
6216
6293
  this.#markHung(true, now);
@@ -6277,6 +6354,7 @@ var CdpKeeper = class {
6277
6354
  async #doRestart(verify, hung = false) {
6278
6355
  this.#restarting = true;
6279
6356
  this.#lastRestart = this.#now();
6357
+ this.#startGrace(this.#lastRestart);
6280
6358
  this.#restarts++;
6281
6359
  this.#saveThrottle();
6282
6360
  try {
@@ -6368,6 +6446,10 @@ var CdpKeeper = class {
6368
6446
  this.#d.log(`\u5524\u8D77 WorkBuddy\uFF1A${Math.round((now - this.#lastWakeLaunch) / 1e3)} \u79D2\u524D\u521A\u62C9\u8D77\u8FC7\uFF08\u8282\u6D41 60 \u79D2\uFF09\uFF0C\u4E0D\u518D\u62C9\u8D77\uFF0C\u7B49\u5B83\u8D77\u6765`);
6369
6447
  return "launched";
6370
6448
  }
6449
+ if (pr === "timeout" && this.#inGrace(now) && await this.ownerOk().catch(() => false)) {
6450
+ this.#noteGrace();
6451
+ return "running";
6452
+ }
6371
6453
  if (await this.#isHung(pr)) return this.#wakeHung(now);
6372
6454
  if (this.#lastRestart && now - this.#lastRestart < RESTART_GAP_MS) {
6373
6455
  this.#d.log(`\u5524\u8D77 WorkBuddy\uFF1A${Math.round((now - this.#lastRestart) / 6e4)} \u5206\u949F\u524D\u521A\u91CD\u542F\u8FC7\uFF08\u8282\u6D41 30 \u5206\u949F\uFF09\uFF0C\u4E0D\u518D\u91CD\u542F\uFF0C\u7B49\u7AEF\u53E3\u8D77\u6765`);
@@ -6384,6 +6466,7 @@ var CdpKeeper = class {
6384
6466
  return "not_installed";
6385
6467
  }
6386
6468
  this.#lastWakeLaunch = now;
6469
+ this.#startGrace(now);
6387
6470
  this.#saveThrottle();
6388
6471
  await this.ensureEnv().catch(() => {
6389
6472
  });
@@ -10648,7 +10731,7 @@ var defaultLoginOpenDeps = () => ({
10648
10731
  });
10649
10732
  async function installCodebuddyHooks(log) {
10650
10733
  try {
10651
- const { installTools: installTools2 } = await import("./enable-OC46BDQD.js");
10734
+ const { installTools: installTools2 } = await import("./enable-UNMUBPWP.js");
10652
10735
  installTools2(["workbuddy"], () => {
10653
10736
  }, null, { auto: true });
10654
10737
  } catch (e) {
@@ -11162,7 +11245,7 @@ function defaultOfferDeps(log) {
11162
11245
  }),
11163
11246
  openLogin: () => defaultOpenLogin(platform),
11164
11247
  installHooks: async () => {
11165
- const { installTools: installTools2 } = await import("./enable-OC46BDQD.js");
11248
+ const { installTools: installTools2 } = await import("./enable-UNMUBPWP.js");
11166
11249
  installTools2(["workbuddy"], (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`), null, { auto: true });
11167
11250
  },
11168
11251
  log: (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`),
package/dist/cli.js CHANGED
@@ -262,7 +262,7 @@ import {
262
262
  writeState,
263
263
  writeWakeMark,
264
264
  writeWakeSecret
265
- } from "./chunk-VSS27ECH.js";
265
+ } from "./chunk-37YGRQ4H.js";
266
266
 
267
267
  // src/commands/config.ts
268
268
  function runConfig(args, out = (s2) => process.stdout.write(s2 + "\n")) {
@@ -795,9 +795,9 @@ var AutoEnabler = class {
795
795
 
796
796
  // src/daemon/index.ts
797
797
  import { randomBytes as randomBytes7 } from "crypto";
798
- import { existsSync as existsSync7, readdirSync as readdirSync11, readFileSync as readFileSync13, statSync as statSync13 } from "fs";
798
+ import { existsSync as existsSync7, readdirSync as readdirSync11, readFileSync as readFileSync14, statSync as statSync13 } from "fs";
799
799
  import { homedir as homedir8 } from "os";
800
- import { basename as basename9, dirname as dirname8, join as join26 } from "path";
800
+ import { basename as basename9, dirname as dirname9, join as join27 } from "path";
801
801
  import WebSocket2 from "ws";
802
802
 
803
803
  // src/core/transcript/locate.ts
@@ -2735,6 +2735,23 @@ function basename5(p) {
2735
2735
  const parts = p.split(/[\\/]+/).filter(Boolean);
2736
2736
  return parts[parts.length - 1] ?? "";
2737
2737
  }
2738
+ function addedTail(toolName, a) {
2739
+ const str6 = (v) => typeof v === "string" ? v : "";
2740
+ let oldS = "";
2741
+ let newS = "";
2742
+ if (toolName === "MultiEdit" && Array.isArray(a.edits) && a.edits.length > 0) {
2743
+ const last = a.edits[a.edits.length - 1];
2744
+ oldS = str6(last?.old_string);
2745
+ newS = str6(last?.new_string);
2746
+ } else if (toolName === "NotebookEdit") newS = str6(a.new_source);
2747
+ else {
2748
+ oldS = str6(a.old_string);
2749
+ newS = str6(a.new_string);
2750
+ }
2751
+ const old = new Set(oldS.split(/\r?\n/).map((l) => squash(l)));
2752
+ const added = newS.split(/\r?\n/).map((l) => squash(l)).filter((l) => l && !old.has(l));
2753
+ return (added[added.length - 1] ?? "").slice(0, 20);
2754
+ }
2738
2755
  function displayFingerprint(toolName, input) {
2739
2756
  const a = input && typeof input === "object" ? input : {};
2740
2757
  const s2 = (v) => typeof v === "string" ? v : "";
@@ -2748,7 +2765,14 @@ function displayFingerprint(toolName, input) {
2748
2765
  const body = squash(s2(a.content)).slice(0, 20);
2749
2766
  return name ? body ? name + FP_SEP + body : name : "";
2750
2767
  }
2751
- if (["Edit", "MultiEdit", "Read", "NotebookEdit"].includes(toolName)) return squash(basename5(s2(a.file_path) || s2(a.notebook_path)));
2768
+ if (["Edit", "MultiEdit", "NotebookEdit"].includes(toolName)) {
2769
+ const name = squash(basename5(s2(a.file_path) || s2(a.notebook_path)));
2770
+ if (!name) return "";
2771
+ if (toolName === "NotebookEdit") return name;
2772
+ const tail4 = addedTail(toolName, a);
2773
+ return tail4 ? name + FP_SEP + tail4 : name;
2774
+ }
2775
+ if (toolName === "Read") return squash(basename5(s2(a.file_path)));
2752
2776
  return headTail(Object.values(a).find((v) => typeof v === "string" && v.trim() !== "") ?? "").slice(0, 200);
2753
2777
  }
2754
2778
  var SETTLE_MS = 800;
@@ -2792,10 +2816,33 @@ function permissionRegion(lines3, toolName) {
2792
2816
  function isPermissionDialog(lines3, toolName, fingerprint2) {
2793
2817
  if (!fingerprint2) return false;
2794
2818
  const region = permissionRegion(lines3, toolName);
2795
- if (!region) return false;
2819
+ if (!region) return scrolledFileDialog(lines3, toolName, fingerprint2);
2796
2820
  const text = squash(joined(region));
2797
2821
  return fingerprint2.split(FP_SEP).every((part) => !!part && text.includes(squash(part)));
2798
2822
  }
2823
+ var SCROLL_TOOLS = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "NotebookEdit"]);
2824
+ var EDIT_QUESTION_RE = /^\s*Do you want to make this edit to (.+)\?\s*$/;
2825
+ function scrolledFileDialog(lines3, toolName, fingerprint2) {
2826
+ if (!SCROLL_TOOLS.has(toolName)) return false;
2827
+ const [name, ...content] = fingerprint2.split(FP_SEP);
2828
+ if (!name || content.length === 0 || content.some((c) => !c)) return false;
2829
+ let q = -1;
2830
+ for (let i = lines3.length - 1; i >= 0; i--) {
2831
+ if (/^\s*Do you want to .+\?\s*$/.test(lines3[i])) {
2832
+ q = i;
2833
+ break;
2834
+ }
2835
+ }
2836
+ if (q < 0) return false;
2837
+ const m = EDIT_QUESTION_RE.exec(lines3[q]);
2838
+ if (!m || squash(basename5(m[1].trim())) !== name) return false;
2839
+ if (lines3.slice(0, q).some((l) => /^\s*●/.test(l))) return false;
2840
+ const after = joined(lines3.slice(q + 1, q + 6));
2841
+ if (!new RegExp(`^\\s*(?:${CURSOR}\\s*)?1\\.\\s+Yes\\s*$`, "m").test(after)) return false;
2842
+ if (!/^\s*(?:[❯>›]\s*)?3\.\s+No\b/m.test(after)) return false;
2843
+ const body = squash(joined(lines3.slice(0, q)));
2844
+ return content.every((c) => body.includes(c));
2845
+ }
2799
2846
  async function answerPermission(io, toolName, fingerprint2, decision) {
2800
2847
  const ok = (l) => isPermissionDialog(l, toolName, fingerprint2);
2801
2848
  if (!await settled(io, ok)) return "not_waiting";
@@ -7909,6 +7956,8 @@ ${PRELUDE3}
7909
7956
 
7910
7957
  // src/daemon/workbuddyApp/watch.ts
7911
7958
  var KEEPER_EVERY_MS3 = 15 * 1e3;
7959
+ var NON_SDK_KEEP_MS = 60 * 1e3;
7960
+ var NO_SDK_REPROBE_MS = 90 * 1e3;
7912
7961
  var PROBE_EVERY_MS3 = 60 * 1e3;
7913
7962
  var SEEN_TTL_MS4 = 60 * 60 * 1e3;
7914
7963
  var CONNECT_BACKOFF_MIN_MS3 = 15 * 1e3;
@@ -7956,6 +8005,10 @@ var WorkbuddyAppWatch = class {
7956
8005
  #modeProbed = false;
7957
8006
  #connectedAt = 0;
7958
8007
  #nonSdkSince = 0;
8008
+ /** 断开时还是非 sdk:记下原起点和断开时刻,短暂断线(< NON_SDK_KEEP_MS)重连后仍非 sdk 就沿用(258 退回:断线清零让快速回落永远攒不够) */
8009
+ #nonSdkKept = null;
8010
+ /** 上次因为「页面里没有 SDK」重连重探候选页的时刻 */
8011
+ #reprobeAt = 0;
7959
8012
  #connectError = "";
7960
8013
  /** 端口检查过没有(第一轮不通也记一行) */
7961
8014
  #portChecked = false;
@@ -8146,7 +8199,7 @@ var WorkbuddyAppWatch = class {
8146
8199
  }
8147
8200
  if (now < this.#connectRetryAt) return;
8148
8201
  try {
8149
- const client = await (this.#d.connect ?? ((p) => connectCdp(p)))(this.#d.port());
8202
+ const client = await (this.#d.connect ?? ((p) => connectCdp(p, { pickSdk: true })))(this.#d.port());
8150
8203
  if (this.#stopped) {
8151
8204
  try {
8152
8205
  client.close();
@@ -8163,7 +8216,8 @@ var WorkbuddyAppWatch = class {
8163
8216
  this.#connectBackoffMs = CONNECT_BACKOFF_MIN_MS3;
8164
8217
  this.#connectRetryAt = 0;
8165
8218
  this.#connectFailLogged = false;
8166
- this.#d.log("WorkBuddy App \u6388\u6743\u6865\u5DF2\u8FDE\u4E0A");
8219
+ const ti = client.target;
8220
+ this.#d.log(`WorkBuddy App \u6388\u6743\u6865\u5DF2\u8FDE\u4E0A${ti ? `\uFF08\u9875\u9762 ${ti.url}\uFF0C\u5019\u9009 ${ti.index + 1}/${ti.candidates}${ti.sdk === false ? "\uFF0C\u5404\u5019\u9009\u9875\u90FD\u6CA1\u6709 SDK" : ""}${ti.ua ? `\uFF0C${ti.ua}` : ""}\uFF09` : ""}`);
8167
8221
  } catch (e) {
8168
8222
  this.#client = null;
8169
8223
  this.#connectError = String(e?.message ?? e).slice(0, 120);
@@ -8187,9 +8241,23 @@ var WorkbuddyAppWatch = class {
8187
8241
  this.#mode = r?.mode === "sdk" ? "sdk" : r?.mode === "dom" ? "dom" : "none";
8188
8242
  this.#modeWhy = this.#mode === "sdk" ? "" : !r ? "no_result" : typeof r.why === "string" ? r.why.slice(0, 120) : "";
8189
8243
  this.#modeProbed = true;
8190
- if (this.#mode === "sdk") this.#nonSdkSince = 0;
8191
- else if (first || prev === "sdk" || !this.#nonSdkSince) this.#nonSdkSince = now;
8244
+ if (this.#mode === "sdk") {
8245
+ this.#nonSdkSince = 0;
8246
+ this.#nonSdkKept = null;
8247
+ } else if (first || prev === "sdk" || !this.#nonSdkSince) {
8248
+ const kept = this.#nonSdkKept;
8249
+ this.#nonSdkSince = first && kept && now - kept.at < NON_SDK_KEEP_MS ? kept.since : now;
8250
+ this.#nonSdkKept = null;
8251
+ }
8192
8252
  if (first || prev !== this.#mode || prevWhy !== this.#modeWhy) this.#d.log(`WorkBuddy App \u6388\u6743\u6865\u6A21\u5F0F\uFF1A${this.#mode}${this.#modeWhy ? `\uFF08${modeWhyText(this.#modeWhy)}\uFF09` : ""}`);
8253
+ const cands = this.#client?.target?.candidates ?? 1;
8254
+ if (this.#mode !== "sdk" && this.#modeWhy === "no_wb" && cands > 1 && this.#nonSdkSince && now - this.#nonSdkSince >= NO_SDK_REPROBE_MS && now - this.#reprobeAt >= NO_SDK_REPROBE_MS) {
8255
+ this.#reprobeAt = now;
8256
+ this.#d.log(`WorkBuddy App \u5404\u5019\u9009\u9875\u90FD\u6CA1\u6709 SDK \u5DF2\u8D85\u8FC7 ${NO_SDK_REPROBE_MS / 1e3} \u79D2\uFF0C\u91CD\u8FDE\u518D\u9010\u9875\u63A2\u4E00\u6B21`);
8257
+ this.#disconnect(REASON_APP_EXITED);
8258
+ this.#connectRetryAt = 0;
8259
+ return;
8260
+ }
8193
8261
  }
8194
8262
  if (!this.#client) return;
8195
8263
  if (this.#mode === "sdk") await this.#sdkRound();
@@ -8208,6 +8276,7 @@ var WorkbuddyAppWatch = class {
8208
8276
  }
8209
8277
  }
8210
8278
  #disconnect(reason) {
8279
+ if (this.#nonSdkSince) this.#nonSdkKept = { since: this.#nonSdkSince, at: this.#now() };
8211
8280
  this.#mode = "none";
8212
8281
  this.#modeWhy = "";
8213
8282
  this.#modeProbed = false;
@@ -8810,6 +8879,7 @@ var REASON_TAKEOVER = "\u5DF2\u6539\u63A8\u9009\u62E9\u9898\u5361";
8810
8879
  var FALLBACK_GRACE_MS = 3e3;
8811
8880
  var FALLBACK_GRACE_APP_MS = 8e3;
8812
8881
  var FALLBACK_BUTTON = "\u53BB\u7535\u8111\u4E0A\u4F5C\u7B54";
8882
+ var NOPORT_STAT = "[stat:qoder_noport_question]";
8813
8883
  var REASON_APP_GONE = REASON_APP_EXITED;
8814
8884
  var REASON_STOPPED = "Qoder App \u5DF2\u65AD\u5F00";
8815
8885
  var RECOMMENDED_RE2 = /\s*\(Recommended\)\s*$/i;
@@ -9025,7 +9095,7 @@ var QoderAppQuestions = class {
9025
9095
  card.markedAt = this.#mark(sid);
9026
9096
  if (noPort && !this.#noPortLogged.has(sid)) {
9027
9097
  this.#noPortLogged.add(sid);
9028
- this.#d.log(`${this.#d.label} App \u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF0CApp \u91CC\u7684\u9009\u62E9\u9898\u63A8\u4E0D\u5230\u8BBE\u5907\uFF0C\u5DF2\u63A8\u63D0\u793A\u5361\uFF08\u4F1A\u8BDD ${sid.slice(0, 8)}\uFF09`);
9098
+ this.#d.log(`${this.#d.label} App \u6CA1\u5E26\u8C03\u8BD5\u7AEF\u53E3\uFF0CApp \u91CC\u7684\u9009\u62E9\u9898\u63A8\u4E0D\u5230\u8BBE\u5907\uFF0C\u5DF2\u63A8\u63D0\u793A\u5361\uFF08\u4F1A\u8BDD ${sid.slice(0, 8)}\uFF09 ${NOPORT_STAT} tool=${this.#d.tool} scene=notice`);
9029
9099
  }
9030
9100
  const req = {
9031
9101
  window: true,
@@ -9307,7 +9377,7 @@ var QoderHookQuestionGate = class {
9307
9377
  const questions = hookQuestions(hook.tool_input);
9308
9378
  if (!questions || hasDuplicateQuestion(questions)) return none;
9309
9379
  if (!await this.#d.userAway(tool)) {
9310
- this.#d.log(`${tag}\uFF1AApp \u6CA1\u8FDE\u4E0A\u8C03\u8BD5\u7AEF\u53E3\uFF0C\u7528\u6237\u5728\u7535\u8111\u524D\uFF0C\u9009\u62E9\u9898\u7167\u5E38\u5728\u7535\u8111\u4E0A\u51FA`);
9380
+ this.#d.log(`${tag}\uFF1AApp \u6CA1\u8FDE\u4E0A\u8C03\u8BD5\u7AEF\u53E3\uFF0C\u7528\u6237\u5728\u7535\u8111\u524D\uFF0C\u9009\u62E9\u9898\u7167\u5E38\u5728\u7535\u8111\u4E0A\u51FA ${NOPORT_STAT} tool=${tool} scene=present`);
9311
9381
  return none;
9312
9382
  }
9313
9383
  if (signal.aborted) return none;
@@ -9319,7 +9389,7 @@ var QoderHookQuestionGate = class {
9319
9389
  const onAbort = () => ac.abort(typeof signal.reason === "string" && signal.reason ? signal.reason : REASON_PC_CANCELLED);
9320
9390
  if (signal.aborted) onAbort();
9321
9391
  else signal.addEventListener("abort", onAbort, { once: true });
9322
- this.#d.log(`${tag}\uFF1AApp \u6CA1\u8FDE\u4E0A\u8C03\u8BD5\u7AEF\u53E3\u3001\u7528\u6237\u4E0D\u5728\u7535\u8111\u524D\uFF0C\u9009\u62E9\u9898\u6302\u8D77\u5E76\u63A8\u8BBE\u5907`);
9392
+ this.#d.log(`${tag}\uFF1AApp \u6CA1\u8FDE\u4E0A\u8C03\u8BD5\u7AEF\u53E3\u3001\u7528\u6237\u4E0D\u5728\u7535\u8111\u524D\uFF0C\u9009\u62E9\u9898\u6302\u8D77\u5E76\u63A8\u8BBE\u5907 ${NOPORT_STAT} tool=${tool} scene=away`);
9323
9393
  const cwd = typeof hook.cwd === "string" ? hook.cwd : "";
9324
9394
  const card = this.#d.gate(
9325
9395
  {
@@ -9601,6 +9671,7 @@ import { resolve as resolve4 } from "path";
9601
9671
  var WB_APP_WAKE_WAIT_MS = 60 * 1e3;
9602
9672
  var WB_APP_WAKE_WAIT_WIN_MS = 120 * 1e3;
9603
9673
  var WB_APP_STUCK_MS = 120 * 1e3;
9674
+ var WB_APP_NO_SDK_MS = 15 * 1e3;
9604
9675
  var WB_APP_CONFIRM_MS = 60 * 1e3;
9605
9676
  var WB_APP_CONFIRM_POLL_MS = 5 * 1e3;
9606
9677
  var WB_APP_LOCATE_WAIT_MS = 20 * 1e3;
@@ -9715,7 +9786,7 @@ var WbAppTasks = class {
9715
9786
  * 计时起点是「模式最近一次变成非 sdk 的时刻」(长期 sdk 时偶尔掉一下不算);页面脚本没结果(no_result,多半是一次超时)
9716
9787
  * 不算:催一次重探,照常等。返回原因;不是这种情况返回 ''
9717
9788
  */
9718
- #stuckWhy(max) {
9789
+ #stuckWhy(max, justWoke = false) {
9719
9790
  const r = this.#readiness();
9720
9791
  if (!r || !r.connected || !r.probed || r.mode === "sdk" || !r.nonSdkSince) return "";
9721
9792
  if (r.modeWhy === "no_result") {
@@ -9723,7 +9794,7 @@ var WbAppTasks = class {
9723
9794
  return "";
9724
9795
  }
9725
9796
  const secs = Math.round((this.#d.now() - r.nonSdkSince) / 1e3);
9726
- if (this.#d.now() - r.nonSdkSince < max) return "";
9797
+ if (this.#d.now() - r.nonSdkSince < (r.modeWhy === "no_wb" && !justWoke ? Math.min(max, WB_APP_NO_SDK_MS) : max)) return "";
9727
9798
  return `\u6388\u6743\u6865\u6A21\u5F0F\u662F ${r.mode} \u5DF2 ${secs} \u79D2${r.modeWhy ? `\uFF08${modeWhyText(r.modeWhy)}\uFF09` : ""}\uFF0CApp \u4E0D\u662F\u521A\u6253\u5F00\uFF0C\u518D\u7B49\u4E5F\u4E0D\u4F1A\u597D`;
9728
9799
  }
9729
9800
  async #ensureReady(cmd, sid, q, report, mayWake) {
@@ -9780,6 +9851,11 @@ var WbAppTasks = class {
9780
9851
  await this.#sleep(Math.max(1, Math.min(this.#d.pollMs ?? WB_APP_POLL_MS, deadline - this.#d.now())));
9781
9852
  const now = await this.#notReady();
9782
9853
  if (!now) return true;
9854
+ const stuckNow = sid ? "" : this.#stuckWhy(WB_APP_STUCK_MS, w === "launched" || w === "restarted");
9855
+ if (stuckNow) {
9856
+ this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} WorkBuddy App \u7528\u4E0D\u4E86\uFF1A${stuckNow}\uFF0C\u8D70\u547D\u4EE4\u884C`);
9857
+ return false;
9858
+ }
9783
9859
  if (now !== why) this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} \u7B49 WorkBuddy App\uFF1A${now}`);
9784
9860
  why = now;
9785
9861
  }
@@ -12665,10 +12741,50 @@ function unwrapShell(command) {
12665
12741
  const w = command.match(/^"?(?:[^"]*[\\/])?(?:powershell|pwsh)(?:\.exe)?"?(?: -NoLogo| -NoProfile| -NonInteractive)* -Command ([\s\S]+)$/i);
12666
12742
  if (!w) return command;
12667
12743
  const body = w[1].trim();
12744
+ const posix4 = posixWord(body);
12745
+ if (posix4 !== null && /^'[^']*'["']/.test(body)) return posix4;
12668
12746
  if (body.length >= 2 && body.startsWith('"') && body.endsWith('"')) return body.slice(1, -1).replace(/\\"/g, '"');
12669
12747
  if (body.length >= 2 && body.startsWith("'") && body.endsWith("'")) return body.slice(1, -1).replace(/''/g, "'");
12670
12748
  return body;
12671
12749
  }
12750
+ function posixWord(s2) {
12751
+ let out = "";
12752
+ let i = 0;
12753
+ while (i < s2.length) {
12754
+ const c = s2[i];
12755
+ if (c === "'") {
12756
+ const j = s2.indexOf("'", i + 1);
12757
+ if (j < 0) return null;
12758
+ out += s2.slice(i + 1, j);
12759
+ i = j + 1;
12760
+ } else if (c === '"') {
12761
+ i++;
12762
+ let closed = false;
12763
+ while (i < s2.length) {
12764
+ const d = s2[i];
12765
+ if (d === '"') {
12766
+ closed = true;
12767
+ i++;
12768
+ break;
12769
+ }
12770
+ if (d === "\\" && i + 1 < s2.length && '\\"$`\n'.includes(s2[i + 1])) {
12771
+ out += s2[i + 1];
12772
+ i += 2;
12773
+ continue;
12774
+ }
12775
+ out += d;
12776
+ i++;
12777
+ }
12778
+ if (!closed) return null;
12779
+ } else if (/\s/.test(c)) {
12780
+ return null;
12781
+ } else {
12782
+ out += c;
12783
+ i++;
12784
+ }
12785
+ }
12786
+ return out;
12787
+ }
12672
12788
  var CodexWatch = class {
12673
12789
  #d;
12674
12790
  #ws = null;
@@ -14046,6 +14162,68 @@ function describe2(r) {
14046
14162
  return [r.status, r.n !== void 0 ? `n=${r.n}` : "", r.detail ?? ""].filter(Boolean).join(" ");
14047
14163
  }
14048
14164
 
14165
+ // src/daemon/dshVersion.ts
14166
+ import { readFileSync as readFileSync11, realpathSync as realpathSync5 } from "fs";
14167
+ import { dirname as dirname8, join as join24 } from "path";
14168
+ var DSH_HEADLESS_MIN_VERSION = "0.0.1-rc.1";
14169
+ var DSH_PKG = "@deepseek-ai/dsh";
14170
+ var DSH_VERSION_UNSUPPORTED = "tool_version_unsupported";
14171
+ var defaultDeps2 = {
14172
+ locate: () => locate("deepseek_harness"),
14173
+ realpath: (p) => realpathSync5.native(p),
14174
+ readFile: (p) => {
14175
+ try {
14176
+ return readFileSync11(p, "utf8");
14177
+ } catch {
14178
+ return null;
14179
+ }
14180
+ }
14181
+ };
14182
+ function dshSupport(d = defaultDeps2) {
14183
+ let loc = null;
14184
+ try {
14185
+ loc = d.locate();
14186
+ } catch {
14187
+ loc = null;
14188
+ }
14189
+ if (!loc) return { ok: true };
14190
+ const script = loc.prefixArgs.find((a) => /\.(?:c|m)?js$/i.test(a)) ?? loc.file;
14191
+ let real2 = script;
14192
+ try {
14193
+ real2 = d.realpath(script);
14194
+ } catch {
14195
+ real2 = script;
14196
+ }
14197
+ let dir2 = dirname8(real2);
14198
+ for (let i = 0; i < 4; i++) {
14199
+ const raw = d.readFile(join24(dir2, "package.json"));
14200
+ if (raw !== null) {
14201
+ let pkg = {};
14202
+ try {
14203
+ pkg = JSON.parse(raw);
14204
+ } catch {
14205
+ pkg = {};
14206
+ }
14207
+ const name = typeof pkg.name === "string" ? pkg.name : "";
14208
+ const version = typeof pkg.version === "string" ? pkg.version : "";
14209
+ if (!name) {
14210
+ dir2 = dirname8(dir2);
14211
+ continue;
14212
+ }
14213
+ if (name !== DSH_PKG) return { ok: true };
14214
+ if (!isValidVersion(version) || semverLt(version, DSH_HEADLESS_MIN_VERSION)) return { ok: false, why: `${DSH_PKG} ${version || "\u7248\u672C\u8BFB\u4E0D\u51FA"} \u4F4E\u4E8E ${DSH_HEADLESS_MIN_VERSION}` };
14215
+ return { ok: true, version };
14216
+ }
14217
+ const up = dirname8(dir2);
14218
+ if (up === dir2) break;
14219
+ dir2 = up;
14220
+ }
14221
+ return { ok: true };
14222
+ }
14223
+ function dshOutputUnsupported(out) {
14224
+ return /unknown (?:command|option|argument)[^\n]*(?:headless|profile)|(?:invalid|no such) (?:command|choice)[^\n]*(?:headless|profile)|unrecognized arguments?:[^\n]*(?:--profile|headless)/i.test(out);
14225
+ }
14226
+
14049
14227
  // src/daemon/opencodeVersion.ts
14050
14228
  import { execFile as execFile5 } from "child_process";
14051
14229
  import { statSync as statSync12 } from "fs";
@@ -14535,24 +14713,24 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
14535
14713
  }
14536
14714
 
14537
14715
  // src/core/appended.ts
14538
- import { chmodSync as chmodSync4, mkdirSync as mkdirSync11, readFileSync as readFileSync11, renameSync as renameSync8, rmSync as rmSync9, writeFileSync as writeFileSync10 } from "fs";
14539
- import { join as join24 } from "path";
14716
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync11, readFileSync as readFileSync12, renameSync as renameSync8, rmSync as rmSync9, writeFileSync as writeFileSync10 } from "fs";
14717
+ import { join as join25 } from "path";
14540
14718
  var APPENDED_MAX_ITEMS = 5;
14541
14719
  var APPENDED_TEXT_MAX = 500;
14542
14720
  var APPENDED_RESULT_MAX = 800;
14543
14721
  var APPENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
14544
14722
  function appendedDir() {
14545
- return join24(deskbuddyHome(), "appended");
14723
+ return join25(deskbuddyHome(), "appended");
14546
14724
  }
14547
14725
  function appendedPath(sessionId) {
14548
14726
  const safe2 = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
14549
- return safe2 ? join24(appendedDir(), `${safe2}.json`) : null;
14727
+ return safe2 ? join25(appendedDir(), `${safe2}.json`) : null;
14550
14728
  }
14551
14729
  function readAppended(sessionId) {
14552
14730
  const file = appendedPath(sessionId);
14553
14731
  if (!file) return [];
14554
14732
  try {
14555
- const d = JSON.parse(readFileSync11(file, "utf8"));
14733
+ const d = JSON.parse(readFileSync12(file, "utf8"));
14556
14734
  return Array.isArray(d.items) ? d.items : [];
14557
14735
  } catch {
14558
14736
  return [];
@@ -14576,13 +14754,13 @@ function writeAppended(sessionId, item) {
14576
14754
  var RUNNING_TTL_MS = 60 * 60 * 1e3;
14577
14755
  function runningPath(sessionId) {
14578
14756
  const safe2 = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
14579
- return safe2 ? join24(deskbuddyHome(), "running", `${safe2}.json`) : null;
14757
+ return safe2 ? join25(deskbuddyHome(), "running", `${safe2}.json`) : null;
14580
14758
  }
14581
14759
  function writeRunning(sessionId, now = Date.now()) {
14582
14760
  const file = runningPath(sessionId);
14583
14761
  if (!file) return;
14584
14762
  try {
14585
- mkdirSync11(join24(deskbuddyHome(), "running"), { recursive: true, mode: 448 });
14763
+ mkdirSync11(join25(deskbuddyHome(), "running"), { recursive: true, mode: 448 });
14586
14764
  writeFileSync10(`${file}.tmp`, JSON.stringify({ at: now, pid: process.pid }), { encoding: "utf8", mode: 384 });
14587
14765
  renameSync8(`${file}.tmp`, file);
14588
14766
  } catch {
@@ -14629,8 +14807,8 @@ function resultSummary(tool, stdout6) {
14629
14807
  }
14630
14808
 
14631
14809
  // src/core/dshMailbox.ts
14632
- import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync12, readdirSync as readdirSync10, readFileSync as readFileSync12, renameSync as renameSync9, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "fs";
14633
- import { join as join25 } from "path";
14810
+ import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync12, readdirSync as readdirSync10, readFileSync as readFileSync13, renameSync as renameSync9, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "fs";
14811
+ import { join as join26 } from "path";
14634
14812
  var DSH_HEARTBEAT_FRESH_MS = 5 * 1e3;
14635
14813
  var DSH_ACK_TIMEOUT_MS = 8 * 1e3;
14636
14814
  var ACK_POLL_MS = 200;
@@ -14641,10 +14819,10 @@ var REVOKE_RETRY_MS = 50;
14641
14819
  var SID_RE2 = /^[A-Za-z0-9_-]{1,128}$/;
14642
14820
  var IID_RE = /^[A-Za-z0-9_-]{1,128}$/;
14643
14821
  function dshMailboxDir(home = deskbuddyHome(), channel = "dsh") {
14644
- return join25(home, channel);
14822
+ return join26(home, channel);
14645
14823
  }
14646
14824
  function sub(home, name, channel = "dsh") {
14647
- return join25(dshMailboxDir(home, channel), name);
14825
+ return join26(dshMailboxDir(home, channel), name);
14648
14826
  }
14649
14827
  function ensureDir2(p) {
14650
14828
  mkdirSync12(p, { recursive: true, mode: 448 });
@@ -14684,7 +14862,7 @@ function liveHeartbeats(sessionId, d0 = {}) {
14684
14862
  if (!/^\d+\.json$/.test(n)) continue;
14685
14863
  let hb;
14686
14864
  try {
14687
- hb = JSON.parse(readFileSync12(join25(dir2, n), "utf8"));
14865
+ hb = JSON.parse(readFileSync13(join26(dir2, n), "utf8"));
14688
14866
  } catch {
14689
14867
  continue;
14690
14868
  }
@@ -14708,8 +14886,8 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
14708
14886
  const ackDir = sub(d.home, "ack", d.channel);
14709
14887
  ensureDir2(inbox);
14710
14888
  ensureDir2(ackDir);
14711
- const letter = join25(inbox, `${sessionId}.${msg.instructionId}.json`);
14712
- const ackPath = join25(ackDir, `${msg.instructionId}.json`);
14889
+ const letter = join26(inbox, `${sessionId}.${msg.instructionId}.json`);
14890
+ const ackPath = join26(ackDir, `${msg.instructionId}.json`);
14713
14891
  if (existsSync6(letter)) return { ok: false, reason: "inbox_busy" };
14714
14892
  const writtenAt = d.now();
14715
14893
  writeJson(letter, {
@@ -14727,7 +14905,7 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
14727
14905
  if (existsSync6(ackPath)) {
14728
14906
  let ack = { ok: false, reason: "bad_ack" };
14729
14907
  try {
14730
- const v = JSON.parse(readFileSync12(ackPath, "utf8"));
14908
+ const v = JSON.parse(readFileSync13(ackPath, "utf8"));
14731
14909
  if (v && typeof v.ok === "boolean") ack = v;
14732
14910
  } catch {
14733
14911
  }
@@ -14778,10 +14956,10 @@ function dshClearInbox(home = deskbuddyHome(), channel = "dsh") {
14778
14956
  var DSH_REPLY_WAIT_MS = 4 * 60 * 1e3;
14779
14957
  function takeMailboxReply(instructionId, home = deskbuddyHome(), channel = "dsh") {
14780
14958
  if (!IID_RE.test(instructionId)) return null;
14781
- const p = join25(sub(home, "reply", channel), `${instructionId}.json`);
14959
+ const p = join26(sub(home, "reply", channel), `${instructionId}.json`);
14782
14960
  let v = null;
14783
14961
  try {
14784
- v = JSON.parse(readFileSync12(p, "utf8"));
14962
+ v = JSON.parse(readFileSync13(p, "utf8"));
14785
14963
  } catch {
14786
14964
  return null;
14787
14965
  }
@@ -14807,7 +14985,7 @@ function clearDir(inbox) {
14807
14985
  let n = 0;
14808
14986
  for (const f of names) {
14809
14987
  try {
14810
- rmSync10(join25(inbox, f), { force: true });
14988
+ rmSync10(join26(inbox, f), { force: true });
14811
14989
  n++;
14812
14990
  } catch {
14813
14991
  }
@@ -14882,7 +15060,7 @@ function defaultCodexAppServers() {
14882
15060
  }
14883
15061
  function opencodeReady() {
14884
15062
  const mod = ocPluginModule(installedHookPath());
14885
- return isOcPluginInstalled(opencodePluginPath()) && existsSync7(mod) && existsSync7(join26(dirname8(mod), "ocTranslate.mjs"));
15063
+ return isOcPluginInstalled(opencodePluginPath()) && existsSync7(mod) && existsSync7(join27(dirname9(mod), "ocTranslate.mjs"));
14886
15064
  }
14887
15065
  var INTERNAL_PROMPT_MARK = "[DeskBuddy]";
14888
15066
  function orphanResumeText(summary2) {
@@ -15039,6 +15217,7 @@ var Daemon = class {
15039
15217
  materialize: deps2.materialize ?? ((plan, tool) => materialize(plan, tool)),
15040
15218
  opencodeReady: deps2.opencodeReady ?? opencodeReady,
15041
15219
  opencodeVersion: deps2.opencodeVersion ?? defaultOpencodeVersion,
15220
+ dshSupport: deps2.dshSupport ?? (() => dshSupport()),
15042
15221
  codexAppServers: deps2.codexAppServers ?? defaultCodexAppServers,
15043
15222
  codexRoute: deps2.codexRoute ?? ((tid) => this.#codexRouteFor(tid)),
15044
15223
  report: deps2.report ?? ((p, t) => api.commandResult(p, t)),
@@ -16741,7 +16920,7 @@ var Daemon = class {
16741
16920
  for (const n of names) {
16742
16921
  if (!n.endsWith(".json")) continue;
16743
16922
  try {
16744
- const s2 = JSON.parse(readFileSync13(join26(stateDir(), n), "utf8"));
16923
+ const s2 = JSON.parse(readFileSync14(join27(stateDir(), n), "utf8"));
16745
16924
  if (typeof s2.sessionId === "string" && typeof s2.tool === "string" && enabled.has(s2.tool)) out.push({ tool: s2.tool, sessionId: s2.sessionId, state: typeof s2.state === "string" ? s2.state : "", cwd: typeof s2.cwd === "string" ? s2.cwd : "" });
16746
16925
  } catch {
16747
16926
  }
@@ -16775,12 +16954,12 @@ var Daemon = class {
16775
16954
  }
16776
16955
  case "deepseek_harness": {
16777
16956
  if (!TRANSCRIPT_SID_RE.test(s2.sessionId)) return null;
16778
- const p = join26(deskbuddyHome(), "dsh", "transcript", `${s2.sessionId}.jsonl`);
16957
+ const p = join27(deskbuddyHome(), "dsh", "transcript", `${s2.sessionId}.jsonl`);
16779
16958
  return existsSync7(p) ? { kind: "file", path: p, format: "dsh" } : null;
16780
16959
  }
16781
16960
  case "opencode": {
16782
16961
  if (!TRANSCRIPT_SID_RE.test(s2.sessionId)) return null;
16783
- return snapshotSource(`opencode-snap:${s2.sessionId}`, join26(deskbuddyHome(), "opencode", "transcript", `${s2.sessionId}.json`));
16962
+ return snapshotSource(`opencode-snap:${s2.sessionId}`, join27(deskbuddyHome(), "opencode", "transcript", `${s2.sessionId}.json`));
16784
16963
  }
16785
16964
  case "dumate":
16786
16965
  return { kind: "http", key: "dumate", fetch: () => this.#dumate.transcriptMessages(s2.sessionId) };
@@ -16811,7 +16990,7 @@ var Daemon = class {
16811
16990
  #summaryTranscriptOf(st) {
16812
16991
  const f = this.#transcriptOf(st, this.#deps.now());
16813
16992
  if (f) return f;
16814
- const homes = st.tool === "workbuddy" ? [st.configDir, dirname8(workbuddySettingsPath()), codebuddyConfigDir()] : st.tool === "qwenwork" ? [qwenworkDataHome()] : [];
16993
+ const homes = st.tool === "workbuddy" ? [st.configDir, dirname9(workbuddySettingsPath()), codebuddyConfigDir()] : st.tool === "qwenwork" ? [qwenworkDataHome()] : [];
16815
16994
  for (const h of homes) {
16816
16995
  if (!h) continue;
16817
16996
  const p = claudeTranscriptFile(st.sessionId, st.cwd, h);
@@ -17131,6 +17310,19 @@ var Daemon = class {
17131
17310
  return;
17132
17311
  }
17133
17312
  }
17313
+ if (cmd.tool === "deepseek_harness" && !sessionId) {
17314
+ let sup = { ok: true };
17315
+ try {
17316
+ sup = this.#deps.dshSupport();
17317
+ } catch {
17318
+ sup = { ok: true };
17319
+ }
17320
+ if (!sup.ok) {
17321
+ log(`\u6307\u4EE4 ${cmd.instructionId} \u4E0D\u6267\u884C\uFF1A\u7535\u8111\u4E0A\u7684 DeepSeek Harness \u7248\u672C\u592A\u65E7\uFF0C\u4E0D\u652F\u6301\u8BBE\u5907\u65B0\u5EFA\u4EFB\u52A1\uFF08${sup.why}\uFF1B\u9700\u8981 ${DSH_PKG} \u2265 ${DSH_HEADLESS_MIN_VERSION}\uFF09`);
17322
+ this.#report(cmd, "", "rejected", "", DSH_VERSION_UNSUPPORTED);
17323
+ return;
17324
+ }
17325
+ }
17134
17326
  const runId = newRunId();
17135
17327
  const text = isSummary(cmd) ? withSummaryMark(cmd.text, cmd.instructionId) : cmd.text;
17136
17328
  const plan = buildSpawnPlan({ ...cmd, sessionId, text }, cwd, this.#gateTarget(runId), void 0, sessionId ? readState(sessionId)?.configDir : void 0);
@@ -17167,12 +17359,15 @@ var Daemon = class {
17167
17359
  );
17168
17360
  const writerBusy = cmd.tool === "codex" && isCodexWriterBusy(result, cmd.text);
17169
17361
  if (writerBusy) log(`\u6307\u4EE4 ${cmd.instructionId} Codex \u4F1A\u8BDD\u6B63\u5F00\u5728\u7535\u8111\u7A97\u53E3\u91CC\uFF08\u5360\u7740\u5199\uFF09\uFF0C\u540E\u53F0\u7EED\u4E0D\u4E86`);
17362
+ const dshOld = cmd.tool === "deepseek_harness" && !sessionId && dshOutputUnsupported(`${result.stderr ?? ""}
17363
+ ${result.stdout}`);
17364
+ if (dshOld) log(`\u6307\u4EE4 ${cmd.instructionId} \u7535\u8111\u4E0A\u7684 DeepSeek Harness \u7248\u672C\u592A\u65E7\uFF0C\u4E0D\u652F\u6301\u8BBE\u5907\u65B0\u5EFA\u4EFB\u52A1\uFF08\u4E0D\u8BA4 --profile headless\uFF09`);
17170
17365
  this.#report(
17171
17366
  cmd,
17172
17367
  sessionId || result.sessionId || "",
17173
17368
  "failed",
17174
17369
  "headless",
17175
- notLoggedIn ? AGENT_NOT_LOGGED_IN : writerBusy ? "window_open" : "exec_failed"
17370
+ notLoggedIn ? AGENT_NOT_LOGGED_IN : writerBusy ? "window_open" : dshOld ? DSH_VERSION_UNSUPPORTED : "exec_failed"
17176
17371
  );
17177
17372
  if (notLoggedIn) this.#guideLogin(cmd.tool);
17178
17373
  }
@@ -17407,7 +17602,7 @@ async function runDaemon(deps2 = {}) {
17407
17602
  }
17408
17603
 
17409
17604
  // src/daemon/shimCheck.ts
17410
- import { readFileSync as readFileSync14 } from "fs";
17605
+ import { readFileSync as readFileSync15 } from "fs";
17411
17606
  var SHIM_CHECK_FIRST_DELAY_MS = 60 * 1e3;
17412
17607
  var SHIM_CHECK_INTERVAL_MS = 60 * 60 * 1e3;
17413
17608
  var TOOL = "claude_code";
@@ -17503,7 +17698,7 @@ function defaultShimCheckDeps(log2, idle, inj = {}) {
17503
17698
  idle,
17504
17699
  shimInstalled: () => {
17505
17700
  try {
17506
- return readFileSync14(shimFile(probe.binDir, command, "win32"), "utf8").includes(SHIM_MARKER);
17701
+ return readFileSync15(shimFile(probe.binDir, command, "win32"), "utf8").includes(SHIM_MARKER);
17507
17702
  } catch {
17508
17703
  return false;
17509
17704
  }
@@ -17712,7 +17907,7 @@ async function runDisable(tools, opts = {}) {
17712
17907
 
17713
17908
  // src/commands/install.ts
17714
17909
  import { mkdirSync as mkdirSync13, rmSync as rmSync12, writeFileSync as writeFileSync13 } from "fs";
17715
- import { join as join27 } from "path";
17910
+ import { join as join28 } from "path";
17716
17911
 
17717
17912
  // src/commands/setup.ts
17718
17913
  import qrcode from "qrcode-terminal";
@@ -17929,7 +18124,7 @@ function inSandbox(env) {
17929
18124
  }
17930
18125
  function precheckWritable(dirs) {
17931
18126
  for (const dir2 of dirs) {
17932
- const probe = join27(dir2, `.deskbuddy-probe-${process.pid}`);
18127
+ const probe = join28(dir2, `.deskbuddy-probe-${process.pid}`);
17933
18128
  try {
17934
18129
  mkdirSync13(dir2, { recursive: true });
17935
18130
  writeFileSync13(probe, "");
@@ -18339,11 +18534,12 @@ async function runAutoSwitch(opts) {
18339
18534
  // src/wrap/run.ts
18340
18535
  import { randomBytes as randomBytes10 } from "crypto";
18341
18536
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync15, statSync as statSync16, truncateSync as truncateSync3 } from "fs";
18342
- import { join as join30 } from "path";
18537
+ import { join as join31 } from "path";
18343
18538
  import { StringDecoder as StringDecoder2 } from "string_decoder";
18344
18539
 
18345
18540
  // src/wrap/injector.ts
18346
18541
  var SEEN_MAX = 200;
18542
+ var PERMISSION_PENDING = "permission_pending";
18347
18543
  var WINDOW_UNRECOGNIZED = "window_unrecognized";
18348
18544
  var QUIET_MS = 1500;
18349
18545
  var STARTUP_QUIET_MS = 3e3;
@@ -18509,7 +18705,8 @@ var Injector = class {
18509
18705
  return;
18510
18706
  }
18511
18707
  if (job.expireAt > 0 && this.#d.now() > job.expireAt) {
18512
- this.#final(job, { status: "rejected", mode: "window", reason: "expired" });
18708
+ const perm = this.#lastWhy.split(",").includes("permission");
18709
+ this.#final(job, { status: "rejected", mode: "window", reason: perm ? PERMISSION_PENDING : "expired" });
18513
18710
  return;
18514
18711
  }
18515
18712
  if (!this.#owns(job.sessionId)) {
@@ -18684,9 +18881,9 @@ var WithdrawWatch = class {
18684
18881
 
18685
18882
  // src/wrap/passthrough.ts
18686
18883
  import { spawn as spawn3 } from "child_process";
18687
- import { realpathSync as realpathSync5 } from "fs";
18884
+ import { realpathSync as realpathSync6 } from "fs";
18688
18885
  import { constants } from "os";
18689
- import { dirname as dirname9, resolve as resolve7 } from "path";
18886
+ import { dirname as dirname10, resolve as resolve7 } from "path";
18690
18887
  var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
18691
18888
  "agents",
18692
18889
  "attach",
@@ -18742,7 +18939,7 @@ function norm(p, win) {
18742
18939
  }
18743
18940
  function real(p) {
18744
18941
  try {
18745
- return realpathSync5(p);
18942
+ return realpathSync6(p);
18746
18943
  } catch {
18747
18944
  return p;
18748
18945
  }
@@ -18750,7 +18947,7 @@ function real(p) {
18750
18947
  function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
18751
18948
  const win = (d.platform ?? process.platform) === "win32";
18752
18949
  const skipKeys = /* @__PURE__ */ new Set([norm(skip, win), norm(real(skip), win)]);
18753
- const inSkip = (file) => skipKeys.has(norm(dirname9(file), win)) || skipKeys.has(norm(dirname9(real(file)), win));
18950
+ const inSkip = (file) => skipKeys.has(norm(dirname10(file), win)) || skipKeys.has(norm(dirname10(real(file)), win));
18754
18951
  const dd = { ...d, skipDirs: [...d.skipDirs ?? [], skip] };
18755
18952
  const name = SHIM_COMMANDS[tool];
18756
18953
  if (!name) {
@@ -18834,10 +19031,10 @@ function runDirect(real2, args, env = process.env) {
18834
19031
 
18835
19032
  // src/wrap/codexLaunch.ts
18836
19033
  import { execFileSync as execFileSync4, spawn as spawn4 } from "child_process";
18837
- import { closeSync as closeSync9, mkdirSync as mkdirSync14, openSync as openSync9, readdirSync as readdirSync12, readFileSync as readFileSync15, rmSync as rmSync14, statSync as statSync15, truncateSync as truncateSync2, writeFileSync as writeFileSync14, renameSync as renameSync10 } from "fs";
19034
+ import { closeSync as closeSync9, mkdirSync as mkdirSync14, openSync as openSync9, readdirSync as readdirSync12, readFileSync as readFileSync16, rmSync as rmSync14, statSync as statSync15, truncateSync as truncateSync2, writeFileSync as writeFileSync14, renameSync as renameSync10 } from "fs";
18838
19035
  import { request as request2 } from "http";
18839
19036
  import { createServer as createServer5 } from "net";
18840
- import { join as join28 } from "path";
19037
+ import { join as join29 } from "path";
18841
19038
  var TAKEOVER_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "--config", "-m", "--model", "-s", "--sandbox", "-a", "--ask-for-approval", "-C", "--cd", "--enable", "--disable"]);
18842
19039
  var SHORT_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "-m", "-s", "-a", "-C"]);
18843
19040
  var TAKEOVER_FLAGS = /* @__PURE__ */ new Set(["--search", "--no-alt-screen", "--dangerously-bypass-approvals-and-sandbox"]);
@@ -19006,10 +19203,10 @@ async function runCodexWindow(real2, args, d) {
19006
19203
  }
19007
19204
  var MAX_LOG_BYTES2 = 1024 * 1024;
19008
19205
  function appServerLogPath() {
19009
- return join28(deskbuddyHome(), "codex-app-server.log");
19206
+ return join29(deskbuddyHome(), "codex-app-server.log");
19010
19207
  }
19011
19208
  function windowsDir2() {
19012
- return join28(deskbuddyHome(), "codex-windows");
19209
+ return join29(deskbuddyHome(), "codex-windows");
19013
19210
  }
19014
19211
  function pidAlive5(pid) {
19015
19212
  try {
@@ -19022,7 +19219,7 @@ function pidAlive5(pid) {
19022
19219
  function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
19023
19220
  let pid = 0;
19024
19221
  try {
19025
- pid = Number(readFileSync15(pidFile, "utf8").trim());
19222
+ pid = Number(readFileSync16(pidFile, "utf8").trim());
19026
19223
  } catch {
19027
19224
  return false;
19028
19225
  }
@@ -19099,7 +19296,7 @@ function killOwnServer(pid, url, args = freshArgs, kill = (p) => killServer(p))
19099
19296
  if (a && isAppServerArgs(a, url)) kill(pid);
19100
19297
  }
19101
19298
  function recordPath(rec) {
19102
- return join28(windowsDir2(), `${rec.server}.json`);
19299
+ return join29(windowsDir2(), `${rec.server}.json`);
19103
19300
  }
19104
19301
  function writeRecord(rec) {
19105
19302
  try {
@@ -19124,10 +19321,10 @@ function reapWindows(dir2 = windowsDir2(), alive = pidAlive5, args = freshArgs,
19124
19321
  return;
19125
19322
  }
19126
19323
  for (const n of names) {
19127
- const p = join28(dir2, n);
19324
+ const p = join29(dir2, n);
19128
19325
  let rec;
19129
19326
  try {
19130
- rec = JSON.parse(readFileSync15(p, "utf8"));
19327
+ rec = JSON.parse(readFileSync16(p, "utf8"));
19131
19328
  } catch {
19132
19329
  rmSync14(p, { force: true });
19133
19330
  continue;
@@ -19147,7 +19344,7 @@ function reapWindows(dir2 = windowsDir2(), alive = pidAlive5, args = freshArgs,
19147
19344
  function launchLog(line) {
19148
19345
  try {
19149
19346
  mkdirSync14(deskbuddyHome(), { recursive: true });
19150
- writeFileSync14(join28(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
19347
+ writeFileSync14(join29(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
19151
19348
  `, { flag: "a" });
19152
19349
  } catch {
19153
19350
  }
@@ -19172,7 +19369,7 @@ function defaultCodexWindowDeps(base) {
19172
19369
  // src/wrap/promptLaunch.ts
19173
19370
  import { randomBytes as randomBytes8, randomUUID as randomUUID3 } from "crypto";
19174
19371
  import { homedir as homedir10 } from "os";
19175
- import { join as join29 } from "path";
19372
+ import { join as join30 } from "path";
19176
19373
  var FIRST_LINE_WAIT_MS2 = 2 * 60 * 1e3;
19177
19374
  function promptArgs(text, tool = "") {
19178
19375
  if (!text) return [];
@@ -19182,7 +19379,7 @@ function promptArgs(text, tool = "") {
19182
19379
  function promptEnv(tool, env, launcherPid = process.pid, answerWindowId = "") {
19183
19380
  const out = { ...env };
19184
19381
  if (tool === "workbuddy") {
19185
- if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR = join29(homedir10(), ".workbuddy");
19382
+ if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR = join30(homedir10(), ".workbuddy");
19186
19383
  out.DESKBUDDY_LAUNCHER_PID = String(launcherPid);
19187
19384
  out.DESKBUDDY_LAUNCHED_TOOL = tool;
19188
19385
  if (answerWindowId) out.DESKBUDDY_LAUNCHED_WINDOW = answerWindowId;
@@ -19721,7 +19918,7 @@ async function runAnswerWindow(tool, real2, args, env, windowId, d) {
19721
19918
  }
19722
19919
 
19723
19920
  // src/wrap/launchRegister.ts
19724
- import { dirname as dirname10 } from "path";
19921
+ import { dirname as dirname11 } from "path";
19725
19922
  async function launchTranscriptReady(tool, sessionId) {
19726
19923
  try {
19727
19924
  if (tool === "qoder_cn") {
@@ -19729,7 +19926,7 @@ async function launchTranscriptReady(tool, sessionId) {
19729
19926
  return !!p && (await transcriptInfo(p)).title !== "";
19730
19927
  }
19731
19928
  if (tool === "workbuddy") {
19732
- const p = findWbTranscript(dirname10(workbuddySettingsPath()), sessionId);
19929
+ const p = findWbTranscript(dirname11(workbuddySettingsPath()), sessionId);
19733
19930
  return !!p && wbTranscriptInfo(p).title !== "";
19734
19931
  }
19735
19932
  return false;
@@ -19737,8 +19934,8 @@ async function launchTranscriptReady(tool, sessionId) {
19737
19934
  return false;
19738
19935
  }
19739
19936
  }
19740
- var defaultDeps2 = { qoder: (j) => syncQoderApp(j), workbuddy: (j) => registerWorkbuddySession(j) };
19741
- async function launchRegister(tool, sessionId, cwd, firstLine, final, d = defaultDeps2) {
19937
+ var defaultDeps3 = { qoder: (j) => syncQoderApp(j), workbuddy: (j) => registerWorkbuddySession(j) };
19938
+ async function launchRegister(tool, sessionId, cwd, firstLine, final, d = defaultDeps3) {
19742
19939
  if (tool === "workbuddy") return d.workbuddy({ sessionId, cwd, fallbackTitle: firstLine ?? "" });
19743
19940
  if (tool !== "qoder_cn") return "skip tool";
19744
19941
  const r = await d.qoder({ tool, kind: "register", sessionId, cwd, fallbackTitle: firstLine ?? "" });
@@ -19766,7 +19963,7 @@ function childEnv(src, windowId, takeover = true) {
19766
19963
  }
19767
19964
  function wrapLog(line) {
19768
19965
  try {
19769
- const p = join30(deskbuddyHome(), "wrap.log");
19966
+ const p = join31(deskbuddyHome(), "wrap.log");
19770
19967
  mkdirSync15(deskbuddyHome(), { recursive: true });
19771
19968
  try {
19772
19969
  if (statSync16(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
@@ -5,7 +5,7 @@ import {
5
5
  installTools,
6
6
  parseTools,
7
7
  runEnable
8
- } from "./chunk-VSS27ECH.js";
8
+ } from "./chunk-37YGRQ4H.js";
9
9
  export {
10
10
  defaultShimEnvForInstall,
11
11
  installShims,
@@ -308,3 +308,33 @@
308
308
  2026-10-12T06:14:16.558Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
309
309
  2026-10-12T06:14:16.558Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
310
310
  2026-10-12T06:14:19.410Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
311
+ 2026-10-12T07:00:56.655Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
312
+ 2026-10-12T07:01:01.581Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
313
+ 2026-10-12T07:01:01.581Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
314
+ 2026-10-12T07:01:01.681Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
315
+ 2026-10-12T07:01:01.681Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
316
+ 2026-10-12T07:01:04.078Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
317
+ 2026-10-12T07:36:12.685Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
318
+ 2026-10-12T07:36:17.637Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
319
+ 2026-10-12T07:36:17.637Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
320
+ 2026-10-12T07:36:17.734Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
321
+ 2026-10-12T07:36:17.734Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
322
+ 2026-10-12T07:36:20.164Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
323
+ 2026-10-12T07:38:26.140Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
324
+ 2026-10-12T07:38:31.306Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
325
+ 2026-10-12T07:38:31.306Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
326
+ 2026-10-12T07:38:31.422Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
327
+ 2026-10-12T07:38:31.422Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
328
+ 2026-10-12T07:38:34.204Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
329
+ 2026-10-12T07:45:21.768Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
330
+ 2026-10-12T07:45:26.698Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
331
+ 2026-10-12T07:45:26.699Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
332
+ 2026-10-12T07:45:26.806Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
333
+ 2026-10-12T07:45:26.806Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
334
+ 2026-10-12T07:45:29.059Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
335
+ 2026-10-12T07:48:52.563Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
336
+ 2026-10-12T07:48:57.566Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
337
+ 2026-10-12T07:48:57.566Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
338
+ 2026-10-12T07:48:57.673Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
339
+ 2026-10-12T07:48:57.673Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
340
+ 2026-10-12T07:49:00.208Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deskbuddy",
3
- "version": "0.2.0-beta.34",
3
+ "version": "0.2.0-beta.35",
4
4
  "description": "把 Claude Code、Codex 等 AI 编码智能体每个会话窗口的状态,实时同步到桌面硬件屏幕",
5
5
  "keywords": [
6
6
  "ai",