deskbuddy 0.2.0-beta.34 → 0.2.0-beta.36

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";
@@ -2843,13 +2890,40 @@ function singleCursor(lines3, q) {
2843
2890
  if (m) {
2844
2891
  const k = Number(m[1]) - 1;
2845
2892
  if (k >= 0 && k < n) return squash(m[2]).startsWith(squash(q.options[k].label).slice(0, 6)) ? k : -1;
2846
- if (k === n) return /^Typesomething/.test(squash(m[2])) ? n : -1;
2893
+ if (k === n) return /^(Typesomething)?$/.test(squash(m[2])) ? n : -1;
2847
2894
  return -1;
2848
2895
  }
2849
2896
  if (new RegExp(`^\\s*${CURSOR}\\s*(Next|Submit)\\s*$`).test(l)) return n + 1;
2850
2897
  }
2851
2898
  return -1;
2852
2899
  }
2900
+ function typeLine(lines3, n) {
2901
+ const start = questionStart(lines3);
2902
+ if (start < 0) return -1;
2903
+ for (let i = start; i < lines3.length; i++) {
2904
+ const m = lines3[i].match(/^\s*(?:[❯>›]\s*)?[☐☒]?\s*(\d+)\.(?:\s|$)/);
2905
+ if (m && Number(m[1]) === n + 1) return i;
2906
+ }
2907
+ return -1;
2908
+ }
2909
+ function typedText(lines3, n) {
2910
+ const ln = typeLine(lines3, n);
2911
+ if (ln < 0) return null;
2912
+ const parts = [lines3[ln].replace(/^\s*(?:[❯>›]\s*)?[☐☒]?\s*\d+\./, "")];
2913
+ for (let i = ln + 1; i < lines3.length; i++) {
2914
+ if (new RegExp(`^\\s*(?:${CURSOR}\\s*)?(Submit|Next)\\s*$`).test(lines3[i]) || /^\s*(?:[❯>›]\s*)?[☐☒]?\s*\d+\./.test(lines3[i])) break;
2915
+ parts.push(lines3[i]);
2916
+ }
2917
+ return squash(parts.join(""));
2918
+ }
2919
+ async function typeOther(io, q, text) {
2920
+ const n = q.options.length;
2921
+ const want = squash(text);
2922
+ if (!want) return "no_focus";
2923
+ if (!await waitUntil(io, (l) => onQuestion(l, q) && typedText(l, n) === "")) return "no_focus";
2924
+ io.write(text);
2925
+ return await waitUntil(io, (l) => typedText(l, n) === want) ? "typed" : "mismatch";
2926
+ }
2853
2927
  async function move(io, delta) {
2854
2928
  const key = delta > 0 ? KEY_DOWN : KEY_UP;
2855
2929
  for (let i = 0; i < Math.abs(delta); i++) {
@@ -2857,12 +2931,16 @@ async function move(io, delta) {
2857
2931
  await io.sleep(ARROW_GAP_MS);
2858
2932
  }
2859
2933
  }
2860
- async function answerQuestions(io, questions, selections) {
2934
+ async function answerQuestions(io, questions, selections, others) {
2861
2935
  if (questions.length === 0 || selections.length !== questions.length) return "not_waiting";
2936
+ if (others !== void 0 && others.length !== questions.length) return "not_waiting";
2862
2937
  for (let qi = 0; qi < questions.length; qi++) {
2863
2938
  const sel = selections[qi];
2864
2939
  const q = questions[qi];
2865
- if (sel.length === 0 || sel.some((i) => !Number.isInteger(i) || i < 0 || i >= q.options.length)) return "not_waiting";
2940
+ const n = q.options.length;
2941
+ const other = others?.[qi] ?? "";
2942
+ if (sel.length === 0 || sel.some((i) => !Number.isInteger(i) || i < 0 || i > n)) return "not_waiting";
2943
+ if (sel.includes(n) !== (squash(other) !== "")) return "not_waiting";
2866
2944
  if (q.multiSelect !== true && sel.length !== 1) return "not_waiting";
2867
2945
  }
2868
2946
  if (!await settled(io, (l) => onQuestion(l, questions[0]))) return "not_waiting";
@@ -2883,6 +2961,14 @@ async function answerQuestions(io, questions, selections) {
2883
2961
  for (const t of sel) {
2884
2962
  await move(io, t - cur);
2885
2963
  cur = t;
2964
+ if (t === n) {
2965
+ const ty = await typeOther(io, q, others?.[qi] ?? "");
2966
+ if (ty !== "no_focus") wrote = true;
2967
+ if (ty !== "typed") return fail();
2968
+ io.write(KEY_ENTER);
2969
+ if (!await waitUntil(io, (l) => checked(l, typeLine(l, n)))) return fail();
2970
+ continue;
2971
+ }
2886
2972
  const onTarget = await waitUntil(io, (l) => {
2887
2973
  const ln = optionLine(l, t + 1, q.options[t].label);
2888
2974
  return ln >= 0 && io.cursorLine?.() === ln;
@@ -2901,7 +2987,11 @@ async function answerQuestions(io, questions, selections) {
2901
2987
  const cur = singleCursor(io.screen(), q);
2902
2988
  if (cur < 0) return fail();
2903
2989
  await move(io, t - cur);
2904
- if (!await waitUntil(io, (l) => singleCursor(l, q) === t && optionLine(l, t + 1, q.options[t].label) >= 0)) return fail();
2990
+ if (t === n) {
2991
+ const ty = await typeOther(io, q, others?.[qi] ?? "");
2992
+ if (ty !== "no_focus") wrote = true;
2993
+ if (ty !== "typed") return fail();
2994
+ } else if (!await waitUntil(io, (l) => singleCursor(l, q) === t && optionLine(l, t + 1, q.options[t].label) >= 0)) return fail();
2905
2995
  io.write(KEY_ENTER);
2906
2996
  wrote = true;
2907
2997
  }
@@ -7759,7 +7849,14 @@ ${PRELUDE3}
7759
7849
  }
7760
7850
  }
7761
7851
  if (C && typeof C.currentId === 'string') return { mode: 'dom', locale: localeOf(), why };
7762
- return { mode: 'none', locale: localeOf(), why };
7852
+ // 没有 SDK 时列出页面上像接口的全局名(258 退回:WorkBuddy 5.1.7 没有 wb,日志里据此判断老版本把接口放在哪)
7853
+ let globals;
7854
+ if (why === 'no_wb') {
7855
+ try {
7856
+ globals = Object.getOwnPropertyNames(globalThis).filter((k) => /wb|work|buddy|genie|sdk|electron|bridge|ipc|conv|agent|app/i.test(k) && !/^on/.test(k) && k.length <= 40).sort().slice(0, 30);
7857
+ } catch (e) { globals = undefined; }
7858
+ }
7859
+ return { mode: 'none', locale: localeOf(), why, ...(globals ? { globals } : {}) };
7763
7860
  })()`;
7764
7861
  var IDLE_STATES = "['idle','completed','cancelled','canceled','done','failed','error','terminated','archived','deleted','']";
7765
7862
  var SNAPSHOT2 = String.raw`(async () => {
@@ -7909,6 +8006,8 @@ ${PRELUDE3}
7909
8006
 
7910
8007
  // src/daemon/workbuddyApp/watch.ts
7911
8008
  var KEEPER_EVERY_MS3 = 15 * 1e3;
8009
+ var NON_SDK_KEEP_MS = 60 * 1e3;
8010
+ var NO_SDK_REPROBE_MS = 90 * 1e3;
7912
8011
  var PROBE_EVERY_MS3 = 60 * 1e3;
7913
8012
  var SEEN_TTL_MS4 = 60 * 60 * 1e3;
7914
8013
  var CONNECT_BACKOFF_MIN_MS3 = 15 * 1e3;
@@ -7956,6 +8055,10 @@ var WorkbuddyAppWatch = class {
7956
8055
  #modeProbed = false;
7957
8056
  #connectedAt = 0;
7958
8057
  #nonSdkSince = 0;
8058
+ /** 断开时还是非 sdk:记下原起点和断开时刻,短暂断线(< NON_SDK_KEEP_MS)重连后仍非 sdk 就沿用(258 退回:断线清零让快速回落永远攒不够) */
8059
+ #nonSdkKept = null;
8060
+ /** 上次因为「页面里没有 SDK」重连重探候选页的时刻 */
8061
+ #reprobeAt = 0;
7959
8062
  #connectError = "";
7960
8063
  /** 端口检查过没有(第一轮不通也记一行) */
7961
8064
  #portChecked = false;
@@ -8146,7 +8249,7 @@ var WorkbuddyAppWatch = class {
8146
8249
  }
8147
8250
  if (now < this.#connectRetryAt) return;
8148
8251
  try {
8149
- const client = await (this.#d.connect ?? ((p) => connectCdp(p)))(this.#d.port());
8252
+ const client = await (this.#d.connect ?? ((p) => connectCdp(p, { pickSdk: true })))(this.#d.port());
8150
8253
  if (this.#stopped) {
8151
8254
  try {
8152
8255
  client.close();
@@ -8163,7 +8266,8 @@ var WorkbuddyAppWatch = class {
8163
8266
  this.#connectBackoffMs = CONNECT_BACKOFF_MIN_MS3;
8164
8267
  this.#connectRetryAt = 0;
8165
8268
  this.#connectFailLogged = false;
8166
- this.#d.log("WorkBuddy App \u6388\u6743\u6865\u5DF2\u8FDE\u4E0A");
8269
+ const ti = client.target;
8270
+ 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
8271
  } catch (e) {
8168
8272
  this.#client = null;
8169
8273
  this.#connectError = String(e?.message ?? e).slice(0, 120);
@@ -8187,9 +8291,26 @@ var WorkbuddyAppWatch = class {
8187
8291
  this.#mode = r?.mode === "sdk" ? "sdk" : r?.mode === "dom" ? "dom" : "none";
8188
8292
  this.#modeWhy = this.#mode === "sdk" ? "" : !r ? "no_result" : typeof r.why === "string" ? r.why.slice(0, 120) : "";
8189
8293
  this.#modeProbed = true;
8190
- if (this.#mode === "sdk") this.#nonSdkSince = 0;
8191
- else if (first || prev === "sdk" || !this.#nonSdkSince) this.#nonSdkSince = now;
8192
- 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` : ""}`);
8294
+ if (this.#mode === "sdk") {
8295
+ this.#nonSdkSince = 0;
8296
+ this.#nonSdkKept = null;
8297
+ } else if (first || prev === "sdk" || !this.#nonSdkSince) {
8298
+ const kept = this.#nonSdkKept;
8299
+ this.#nonSdkSince = first && kept && now - kept.at < NON_SDK_KEEP_MS ? kept.since : now;
8300
+ this.#nonSdkKept = null;
8301
+ }
8302
+ if (first || prev !== this.#mode || prevWhy !== this.#modeWhy) {
8303
+ const g = Array.isArray(r?.globals) ? r.globals.filter((x) => typeof x === "string").map((x) => x.slice(0, 40)).slice(0, 30) : null;
8304
+ this.#d.log(`WorkBuddy App \u6388\u6743\u6865\u6A21\u5F0F\uFF1A${this.#mode}${this.#modeWhy ? `\uFF08${modeWhyText(this.#modeWhy)}\uFF09` : ""}${g ? `\uFF1B\u9875\u9762\u5168\u5C40\u540D\uFF1A${g.join(",") || "\u65E0"}` : ""}`);
8305
+ }
8306
+ const cands = this.#client?.target?.candidates ?? 1;
8307
+ 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) {
8308
+ this.#reprobeAt = now;
8309
+ 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`);
8310
+ this.#disconnect(REASON_APP_EXITED);
8311
+ this.#connectRetryAt = 0;
8312
+ return;
8313
+ }
8193
8314
  }
8194
8315
  if (!this.#client) return;
8195
8316
  if (this.#mode === "sdk") await this.#sdkRound();
@@ -8208,6 +8329,7 @@ var WorkbuddyAppWatch = class {
8208
8329
  }
8209
8330
  }
8210
8331
  #disconnect(reason) {
8332
+ if (this.#nonSdkSince) this.#nonSdkKept = { since: this.#nonSdkSince, at: this.#now() };
8211
8333
  this.#mode = "none";
8212
8334
  this.#modeWhy = "";
8213
8335
  this.#modeProbed = false;
@@ -8449,7 +8571,7 @@ function modeWhyText(why) {
8449
8571
  const msg = rest.join(":");
8450
8572
  switch (k) {
8451
8573
  case "no_wb":
8452
- return "\u9875\u9762\u91CC\u6CA1\u6709 WorkBuddy SDK\uFF08globalThis.wb\uFF09";
8574
+ return "\u9875\u9762\u91CC\u6CA1\u6709 WorkBuddy SDK\uFF08globalThis.wb\uFF09\uFF0CWorkBuddy \u7248\u672C\u53EF\u80FD\u592A\u65E7\uFF0C\u5347\u7EA7\u5230\u6700\u65B0\u7248\u540E\u4EFB\u52A1\u624D\u80FD\u76F4\u63A5\u8FDB App";
8453
8575
  case "no_conversations":
8454
8576
  return "\u9875\u9762\u91CC\u6CA1\u6709\u4F1A\u8BDD\u63A5\u53E3\uFF08wb.conversations\uFF09";
8455
8577
  case "no_list_get":
@@ -8810,6 +8932,7 @@ var REASON_TAKEOVER = "\u5DF2\u6539\u63A8\u9009\u62E9\u9898\u5361";
8810
8932
  var FALLBACK_GRACE_MS = 3e3;
8811
8933
  var FALLBACK_GRACE_APP_MS = 8e3;
8812
8934
  var FALLBACK_BUTTON = "\u53BB\u7535\u8111\u4E0A\u4F5C\u7B54";
8935
+ var NOPORT_STAT = "[stat:qoder_noport_question]";
8813
8936
  var REASON_APP_GONE = REASON_APP_EXITED;
8814
8937
  var REASON_STOPPED = "Qoder App \u5DF2\u65AD\u5F00";
8815
8938
  var RECOMMENDED_RE2 = /\s*\(Recommended\)\s*$/i;
@@ -9025,7 +9148,7 @@ var QoderAppQuestions = class {
9025
9148
  card.markedAt = this.#mark(sid);
9026
9149
  if (noPort && !this.#noPortLogged.has(sid)) {
9027
9150
  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`);
9151
+ 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
9152
  }
9030
9153
  const req = {
9031
9154
  window: true,
@@ -9307,7 +9430,7 @@ var QoderHookQuestionGate = class {
9307
9430
  const questions = hookQuestions(hook.tool_input);
9308
9431
  if (!questions || hasDuplicateQuestion(questions)) return none;
9309
9432
  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`);
9433
+ 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
9434
  return none;
9312
9435
  }
9313
9436
  if (signal.aborted) return none;
@@ -9319,7 +9442,7 @@ var QoderHookQuestionGate = class {
9319
9442
  const onAbort = () => ac.abort(typeof signal.reason === "string" && signal.reason ? signal.reason : REASON_PC_CANCELLED);
9320
9443
  if (signal.aborted) onAbort();
9321
9444
  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`);
9445
+ 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
9446
  const cwd = typeof hook.cwd === "string" ? hook.cwd : "";
9324
9447
  const card = this.#d.gate(
9325
9448
  {
@@ -9601,6 +9724,7 @@ import { resolve as resolve4 } from "path";
9601
9724
  var WB_APP_WAKE_WAIT_MS = 60 * 1e3;
9602
9725
  var WB_APP_WAKE_WAIT_WIN_MS = 120 * 1e3;
9603
9726
  var WB_APP_STUCK_MS = 120 * 1e3;
9727
+ var WB_APP_NO_SDK_MS = 15 * 1e3;
9604
9728
  var WB_APP_CONFIRM_MS = 60 * 1e3;
9605
9729
  var WB_APP_CONFIRM_POLL_MS = 5 * 1e3;
9606
9730
  var WB_APP_LOCATE_WAIT_MS = 20 * 1e3;
@@ -9715,7 +9839,7 @@ var WbAppTasks = class {
9715
9839
  * 计时起点是「模式最近一次变成非 sdk 的时刻」(长期 sdk 时偶尔掉一下不算);页面脚本没结果(no_result,多半是一次超时)
9716
9840
  * 不算:催一次重探,照常等。返回原因;不是这种情况返回 ''
9717
9841
  */
9718
- #stuckWhy(max) {
9842
+ #stuckWhy(max, justWoke = false) {
9719
9843
  const r = this.#readiness();
9720
9844
  if (!r || !r.connected || !r.probed || r.mode === "sdk" || !r.nonSdkSince) return "";
9721
9845
  if (r.modeWhy === "no_result") {
@@ -9723,7 +9847,7 @@ var WbAppTasks = class {
9723
9847
  return "";
9724
9848
  }
9725
9849
  const secs = Math.round((this.#d.now() - r.nonSdkSince) / 1e3);
9726
- if (this.#d.now() - r.nonSdkSince < max) return "";
9850
+ if (this.#d.now() - r.nonSdkSince < (r.modeWhy === "no_wb" && !justWoke ? Math.min(max, WB_APP_NO_SDK_MS) : max)) return "";
9727
9851
  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
9852
  }
9729
9853
  async #ensureReady(cmd, sid, q, report, mayWake) {
@@ -9780,6 +9904,11 @@ var WbAppTasks = class {
9780
9904
  await this.#sleep(Math.max(1, Math.min(this.#d.pollMs ?? WB_APP_POLL_MS, deadline - this.#d.now())));
9781
9905
  const now = await this.#notReady();
9782
9906
  if (!now) return true;
9907
+ const stuckNow = sid ? "" : this.#stuckWhy(WB_APP_STUCK_MS, w === "launched" || w === "restarted");
9908
+ if (stuckNow) {
9909
+ this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} WorkBuddy App \u7528\u4E0D\u4E86\uFF1A${stuckNow}\uFF0C\u8D70\u547D\u4EE4\u884C`);
9910
+ return false;
9911
+ }
9783
9912
  if (now !== why) this.#d.log(`\u6307\u4EE4 ${cmd.instructionId} \u7B49 WorkBuddy App\uFF1A${now}`);
9784
9913
  why = now;
9785
9914
  }
@@ -10379,14 +10508,28 @@ function parseAnswer(m) {
10379
10508
  const questions = parseQuestions(a.questions);
10380
10509
  const sel = a.selections;
10381
10510
  if (!questions || sel.length !== questions.length) return null;
10511
+ let others;
10512
+ if (a.others !== void 0) {
10513
+ if (!Array.isArray(a.others) || a.others.length !== questions.length) return null;
10514
+ others = [];
10515
+ for (const o of a.others) {
10516
+ if (typeof o !== "string" || o.length > OTHER_MAX_CHARS || /[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2066-\u2069\ufeff]/.test(o)) return null;
10517
+ others.push(o);
10518
+ }
10519
+ }
10382
10520
  const selections = [];
10383
10521
  for (let i = 0; i < questions.length; i++) {
10384
10522
  const s2 = sel[i];
10385
- if (!Array.isArray(s2) || s2.length === 0 || !s2.every((x) => Number.isInteger(x) && x >= 0 && x < questions[i].options.length)) return null;
10523
+ const n = questions[i].options.length;
10524
+ const other = others?.[i] ?? "";
10525
+ if (!Array.isArray(s2) || s2.length === 0 || !s2.every((x) => Number.isInteger(x) && x >= 0 && x <= n)) return null;
10526
+ if (s2.includes(n) !== (other.trim() !== "")) return null;
10527
+ if (new Set(s2).size !== s2.length) return null;
10386
10528
  selections.push(s2);
10387
10529
  }
10388
- return { instructionId, sessionId, expireAt, answer: { kind: "question", questions, selections } };
10530
+ return { instructionId, sessionId, expireAt, answer: { kind: "question", questions, selections, ...others ? { others } : {} } };
10389
10531
  }
10532
+ var OTHER_MAX_CHARS = 500;
10390
10533
  function parseQuestions(v) {
10391
10534
  if (!Array.isArray(v)) return null;
10392
10535
  const qs = v;
@@ -10752,20 +10895,31 @@ function pendingCall(text, toolName) {
10752
10895
  function selectionsOf(input, lists) {
10753
10896
  const qs = Array.isArray(input.questions) ? input.questions : null;
10754
10897
  if (!qs || !Array.isArray(lists) || lists.length !== qs.length) return null;
10755
- const out = [];
10898
+ const selections = [];
10899
+ const others = [];
10756
10900
  for (let i = 0; i < qs.length; i++) {
10757
10901
  const opts = Array.isArray(qs[i]?.options) ? qs[i].options : [];
10758
10902
  const items = lists[i];
10759
10903
  if (!Array.isArray(items) || items.length === 0) return null;
10760
10904
  const idx = [];
10761
- for (const label of items) {
10905
+ let other = "";
10906
+ for (let j = 0; j < items.length; j++) {
10907
+ const label = items[j];
10762
10908
  const k = opts.findIndex((o) => o?.label === label);
10763
- if (k < 0) return null;
10764
- idx.push(k);
10909
+ if (k >= 0) {
10910
+ if (!idx.includes(k)) idx.push(k);
10911
+ continue;
10912
+ }
10913
+ if (j !== items.length - 1 || typeof label !== "string") return null;
10914
+ other = label.replace(/[\u0000-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2066-\u2069\ufeff]+/g, " ").replace(/\s+/g, " ").trim();
10915
+ if (!other) return null;
10765
10916
  }
10766
- out.push(idx);
10917
+ if (other) idx.push(opts.length);
10918
+ if (qs[i]?.multiSelect !== true && idx.length !== 1) return null;
10919
+ selections.push(idx);
10920
+ others.push(other);
10767
10921
  }
10768
- return out;
10922
+ return { selections, others };
10769
10923
  }
10770
10924
  var WorkbuddyBridge = class {
10771
10925
  #d;
@@ -10903,10 +11057,16 @@ var WorkbuddyBridge = class {
10903
11057
  if (reply.decision !== "allow") return;
10904
11058
  const sel = selectionsOf(card.input, reply.answerLists);
10905
11059
  if (!sel) {
10906
- this.#d.log(`WorkBuddy\uFF1A\u4F1A\u8BDD ${card.sessionId.slice(0, 8)} \u7684\u8BBE\u5907\u7B54\u6848\u5BF9\u4E0D\u4E0A\u9009\u9879\uFF08\u542B\u81EA\u5DF1\u586B\u5199\u7684\u7B54\u6848\uFF09\uFF0C\u6846\u7559\u7ED9\u7535\u8111`);
11060
+ this.#d.log(`WorkBuddy\uFF1A\u4F1A\u8BDD ${card.sessionId.slice(0, 8)} \u7684\u8BBE\u5907\u7B54\u6848\u5BF9\u4E0D\u4E0A\u9009\u9879\uFF0C\u6846\u7559\u7ED9\u7535\u8111`);
10907
11061
  return;
10908
11062
  }
10909
- spec = { kind: "question", questions: card.input.questions, selections: sel };
11063
+ const withOther = sel.others.some((o) => o !== "");
11064
+ spec = {
11065
+ kind: "question",
11066
+ questions: card.input.questions,
11067
+ selections: sel.selections,
11068
+ ...withOther ? { others: sel.others } : {}
11069
+ };
10910
11070
  } else if (reply.decision === "allow" || reply.decision === "deny") {
10911
11071
  spec = { kind: "permission", toolName: card.toolName, fingerprint: card.fingerprint, decision: reply.decision };
10912
11072
  } else return;
@@ -12665,10 +12825,50 @@ function unwrapShell(command) {
12665
12825
  const w = command.match(/^"?(?:[^"]*[\\/])?(?:powershell|pwsh)(?:\.exe)?"?(?: -NoLogo| -NoProfile| -NonInteractive)* -Command ([\s\S]+)$/i);
12666
12826
  if (!w) return command;
12667
12827
  const body = w[1].trim();
12828
+ const posix4 = posixWord(body);
12829
+ if (posix4 !== null && /^'[^']*'["']/.test(body)) return posix4;
12668
12830
  if (body.length >= 2 && body.startsWith('"') && body.endsWith('"')) return body.slice(1, -1).replace(/\\"/g, '"');
12669
12831
  if (body.length >= 2 && body.startsWith("'") && body.endsWith("'")) return body.slice(1, -1).replace(/''/g, "'");
12670
12832
  return body;
12671
12833
  }
12834
+ function posixWord(s2) {
12835
+ let out = "";
12836
+ let i = 0;
12837
+ while (i < s2.length) {
12838
+ const c = s2[i];
12839
+ if (c === "'") {
12840
+ const j = s2.indexOf("'", i + 1);
12841
+ if (j < 0) return null;
12842
+ out += s2.slice(i + 1, j);
12843
+ i = j + 1;
12844
+ } else if (c === '"') {
12845
+ i++;
12846
+ let closed = false;
12847
+ while (i < s2.length) {
12848
+ const d = s2[i];
12849
+ if (d === '"') {
12850
+ closed = true;
12851
+ i++;
12852
+ break;
12853
+ }
12854
+ if (d === "\\" && i + 1 < s2.length && '\\"$`\n'.includes(s2[i + 1])) {
12855
+ out += s2[i + 1];
12856
+ i += 2;
12857
+ continue;
12858
+ }
12859
+ out += d;
12860
+ i++;
12861
+ }
12862
+ if (!closed) return null;
12863
+ } else if (/\s/.test(c)) {
12864
+ return null;
12865
+ } else {
12866
+ out += c;
12867
+ i++;
12868
+ }
12869
+ }
12870
+ return out;
12871
+ }
12672
12872
  var CodexWatch = class {
12673
12873
  #d;
12674
12874
  #ws = null;
@@ -14046,6 +14246,68 @@ function describe2(r) {
14046
14246
  return [r.status, r.n !== void 0 ? `n=${r.n}` : "", r.detail ?? ""].filter(Boolean).join(" ");
14047
14247
  }
14048
14248
 
14249
+ // src/daemon/dshVersion.ts
14250
+ import { readFileSync as readFileSync11, realpathSync as realpathSync5 } from "fs";
14251
+ import { dirname as dirname8, join as join24 } from "path";
14252
+ var DSH_HEADLESS_MIN_VERSION = "0.0.1-rc.1";
14253
+ var DSH_PKG = "@deepseek-ai/dsh";
14254
+ var DSH_VERSION_UNSUPPORTED = "tool_version_unsupported";
14255
+ var defaultDeps2 = {
14256
+ locate: () => locate("deepseek_harness"),
14257
+ realpath: (p) => realpathSync5.native(p),
14258
+ readFile: (p) => {
14259
+ try {
14260
+ return readFileSync11(p, "utf8");
14261
+ } catch {
14262
+ return null;
14263
+ }
14264
+ }
14265
+ };
14266
+ function dshSupport(d = defaultDeps2) {
14267
+ let loc = null;
14268
+ try {
14269
+ loc = d.locate();
14270
+ } catch {
14271
+ loc = null;
14272
+ }
14273
+ if (!loc) return { ok: true };
14274
+ const script = loc.prefixArgs.find((a) => /\.(?:c|m)?js$/i.test(a)) ?? loc.file;
14275
+ let real2 = script;
14276
+ try {
14277
+ real2 = d.realpath(script);
14278
+ } catch {
14279
+ real2 = script;
14280
+ }
14281
+ let dir2 = dirname8(real2);
14282
+ for (let i = 0; i < 4; i++) {
14283
+ const raw = d.readFile(join24(dir2, "package.json"));
14284
+ if (raw !== null) {
14285
+ let pkg = {};
14286
+ try {
14287
+ pkg = JSON.parse(raw);
14288
+ } catch {
14289
+ pkg = {};
14290
+ }
14291
+ const name = typeof pkg.name === "string" ? pkg.name : "";
14292
+ const version = typeof pkg.version === "string" ? pkg.version : "";
14293
+ if (!name) {
14294
+ dir2 = dirname8(dir2);
14295
+ continue;
14296
+ }
14297
+ if (name !== DSH_PKG) return { ok: true };
14298
+ 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}` };
14299
+ return { ok: true, version };
14300
+ }
14301
+ const up = dirname8(dir2);
14302
+ if (up === dir2) break;
14303
+ dir2 = up;
14304
+ }
14305
+ return { ok: true };
14306
+ }
14307
+ function dshOutputUnsupported(out) {
14308
+ return /unknown (?:command|option|argument)[^\n]*(?:headless|profile)|(?:invalid|no such) (?:command|choice)[^\n]*(?:headless|profile)|unrecognized arguments?:[^\n]*(?:--profile|headless)/i.test(out);
14309
+ }
14310
+
14049
14311
  // src/daemon/opencodeVersion.ts
14050
14312
  import { execFile as execFile5 } from "child_process";
14051
14313
  import { statSync as statSync12 } from "fs";
@@ -14535,24 +14797,24 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
14535
14797
  }
14536
14798
 
14537
14799
  // 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";
14800
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync11, readFileSync as readFileSync12, renameSync as renameSync8, rmSync as rmSync9, writeFileSync as writeFileSync10 } from "fs";
14801
+ import { join as join25 } from "path";
14540
14802
  var APPENDED_MAX_ITEMS = 5;
14541
14803
  var APPENDED_TEXT_MAX = 500;
14542
14804
  var APPENDED_RESULT_MAX = 800;
14543
14805
  var APPENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
14544
14806
  function appendedDir() {
14545
- return join24(deskbuddyHome(), "appended");
14807
+ return join25(deskbuddyHome(), "appended");
14546
14808
  }
14547
14809
  function appendedPath(sessionId) {
14548
14810
  const safe2 = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
14549
- return safe2 ? join24(appendedDir(), `${safe2}.json`) : null;
14811
+ return safe2 ? join25(appendedDir(), `${safe2}.json`) : null;
14550
14812
  }
14551
14813
  function readAppended(sessionId) {
14552
14814
  const file = appendedPath(sessionId);
14553
14815
  if (!file) return [];
14554
14816
  try {
14555
- const d = JSON.parse(readFileSync11(file, "utf8"));
14817
+ const d = JSON.parse(readFileSync12(file, "utf8"));
14556
14818
  return Array.isArray(d.items) ? d.items : [];
14557
14819
  } catch {
14558
14820
  return [];
@@ -14576,13 +14838,13 @@ function writeAppended(sessionId, item) {
14576
14838
  var RUNNING_TTL_MS = 60 * 60 * 1e3;
14577
14839
  function runningPath(sessionId) {
14578
14840
  const safe2 = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
14579
- return safe2 ? join24(deskbuddyHome(), "running", `${safe2}.json`) : null;
14841
+ return safe2 ? join25(deskbuddyHome(), "running", `${safe2}.json`) : null;
14580
14842
  }
14581
14843
  function writeRunning(sessionId, now = Date.now()) {
14582
14844
  const file = runningPath(sessionId);
14583
14845
  if (!file) return;
14584
14846
  try {
14585
- mkdirSync11(join24(deskbuddyHome(), "running"), { recursive: true, mode: 448 });
14847
+ mkdirSync11(join25(deskbuddyHome(), "running"), { recursive: true, mode: 448 });
14586
14848
  writeFileSync10(`${file}.tmp`, JSON.stringify({ at: now, pid: process.pid }), { encoding: "utf8", mode: 384 });
14587
14849
  renameSync8(`${file}.tmp`, file);
14588
14850
  } catch {
@@ -14629,8 +14891,8 @@ function resultSummary(tool, stdout6) {
14629
14891
  }
14630
14892
 
14631
14893
  // 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";
14894
+ 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";
14895
+ import { join as join26 } from "path";
14634
14896
  var DSH_HEARTBEAT_FRESH_MS = 5 * 1e3;
14635
14897
  var DSH_ACK_TIMEOUT_MS = 8 * 1e3;
14636
14898
  var ACK_POLL_MS = 200;
@@ -14641,10 +14903,10 @@ var REVOKE_RETRY_MS = 50;
14641
14903
  var SID_RE2 = /^[A-Za-z0-9_-]{1,128}$/;
14642
14904
  var IID_RE = /^[A-Za-z0-9_-]{1,128}$/;
14643
14905
  function dshMailboxDir(home = deskbuddyHome(), channel = "dsh") {
14644
- return join25(home, channel);
14906
+ return join26(home, channel);
14645
14907
  }
14646
14908
  function sub(home, name, channel = "dsh") {
14647
- return join25(dshMailboxDir(home, channel), name);
14909
+ return join26(dshMailboxDir(home, channel), name);
14648
14910
  }
14649
14911
  function ensureDir2(p) {
14650
14912
  mkdirSync12(p, { recursive: true, mode: 448 });
@@ -14684,7 +14946,7 @@ function liveHeartbeats(sessionId, d0 = {}) {
14684
14946
  if (!/^\d+\.json$/.test(n)) continue;
14685
14947
  let hb;
14686
14948
  try {
14687
- hb = JSON.parse(readFileSync12(join25(dir2, n), "utf8"));
14949
+ hb = JSON.parse(readFileSync13(join26(dir2, n), "utf8"));
14688
14950
  } catch {
14689
14951
  continue;
14690
14952
  }
@@ -14708,8 +14970,8 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
14708
14970
  const ackDir = sub(d.home, "ack", d.channel);
14709
14971
  ensureDir2(inbox);
14710
14972
  ensureDir2(ackDir);
14711
- const letter = join25(inbox, `${sessionId}.${msg.instructionId}.json`);
14712
- const ackPath = join25(ackDir, `${msg.instructionId}.json`);
14973
+ const letter = join26(inbox, `${sessionId}.${msg.instructionId}.json`);
14974
+ const ackPath = join26(ackDir, `${msg.instructionId}.json`);
14713
14975
  if (existsSync6(letter)) return { ok: false, reason: "inbox_busy" };
14714
14976
  const writtenAt = d.now();
14715
14977
  writeJson(letter, {
@@ -14727,7 +14989,7 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
14727
14989
  if (existsSync6(ackPath)) {
14728
14990
  let ack = { ok: false, reason: "bad_ack" };
14729
14991
  try {
14730
- const v = JSON.parse(readFileSync12(ackPath, "utf8"));
14992
+ const v = JSON.parse(readFileSync13(ackPath, "utf8"));
14731
14993
  if (v && typeof v.ok === "boolean") ack = v;
14732
14994
  } catch {
14733
14995
  }
@@ -14778,10 +15040,10 @@ function dshClearInbox(home = deskbuddyHome(), channel = "dsh") {
14778
15040
  var DSH_REPLY_WAIT_MS = 4 * 60 * 1e3;
14779
15041
  function takeMailboxReply(instructionId, home = deskbuddyHome(), channel = "dsh") {
14780
15042
  if (!IID_RE.test(instructionId)) return null;
14781
- const p = join25(sub(home, "reply", channel), `${instructionId}.json`);
15043
+ const p = join26(sub(home, "reply", channel), `${instructionId}.json`);
14782
15044
  let v = null;
14783
15045
  try {
14784
- v = JSON.parse(readFileSync12(p, "utf8"));
15046
+ v = JSON.parse(readFileSync13(p, "utf8"));
14785
15047
  } catch {
14786
15048
  return null;
14787
15049
  }
@@ -14807,7 +15069,7 @@ function clearDir(inbox) {
14807
15069
  let n = 0;
14808
15070
  for (const f of names) {
14809
15071
  try {
14810
- rmSync10(join25(inbox, f), { force: true });
15072
+ rmSync10(join26(inbox, f), { force: true });
14811
15073
  n++;
14812
15074
  } catch {
14813
15075
  }
@@ -14882,7 +15144,7 @@ function defaultCodexAppServers() {
14882
15144
  }
14883
15145
  function opencodeReady() {
14884
15146
  const mod = ocPluginModule(installedHookPath());
14885
- return isOcPluginInstalled(opencodePluginPath()) && existsSync7(mod) && existsSync7(join26(dirname8(mod), "ocTranslate.mjs"));
15147
+ return isOcPluginInstalled(opencodePluginPath()) && existsSync7(mod) && existsSync7(join27(dirname9(mod), "ocTranslate.mjs"));
14886
15148
  }
14887
15149
  var INTERNAL_PROMPT_MARK = "[DeskBuddy]";
14888
15150
  function orphanResumeText(summary2) {
@@ -15039,6 +15301,7 @@ var Daemon = class {
15039
15301
  materialize: deps2.materialize ?? ((plan, tool) => materialize(plan, tool)),
15040
15302
  opencodeReady: deps2.opencodeReady ?? opencodeReady,
15041
15303
  opencodeVersion: deps2.opencodeVersion ?? defaultOpencodeVersion,
15304
+ dshSupport: deps2.dshSupport ?? (() => dshSupport()),
15042
15305
  codexAppServers: deps2.codexAppServers ?? defaultCodexAppServers,
15043
15306
  codexRoute: deps2.codexRoute ?? ((tid) => this.#codexRouteFor(tid)),
15044
15307
  report: deps2.report ?? ((p, t) => api.commandResult(p, t)),
@@ -16741,7 +17004,7 @@ var Daemon = class {
16741
17004
  for (const n of names) {
16742
17005
  if (!n.endsWith(".json")) continue;
16743
17006
  try {
16744
- const s2 = JSON.parse(readFileSync13(join26(stateDir(), n), "utf8"));
17007
+ const s2 = JSON.parse(readFileSync14(join27(stateDir(), n), "utf8"));
16745
17008
  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
17009
  } catch {
16747
17010
  }
@@ -16775,12 +17038,12 @@ var Daemon = class {
16775
17038
  }
16776
17039
  case "deepseek_harness": {
16777
17040
  if (!TRANSCRIPT_SID_RE.test(s2.sessionId)) return null;
16778
- const p = join26(deskbuddyHome(), "dsh", "transcript", `${s2.sessionId}.jsonl`);
17041
+ const p = join27(deskbuddyHome(), "dsh", "transcript", `${s2.sessionId}.jsonl`);
16779
17042
  return existsSync7(p) ? { kind: "file", path: p, format: "dsh" } : null;
16780
17043
  }
16781
17044
  case "opencode": {
16782
17045
  if (!TRANSCRIPT_SID_RE.test(s2.sessionId)) return null;
16783
- return snapshotSource(`opencode-snap:${s2.sessionId}`, join26(deskbuddyHome(), "opencode", "transcript", `${s2.sessionId}.json`));
17046
+ return snapshotSource(`opencode-snap:${s2.sessionId}`, join27(deskbuddyHome(), "opencode", "transcript", `${s2.sessionId}.json`));
16784
17047
  }
16785
17048
  case "dumate":
16786
17049
  return { kind: "http", key: "dumate", fetch: () => this.#dumate.transcriptMessages(s2.sessionId) };
@@ -16811,7 +17074,7 @@ var Daemon = class {
16811
17074
  #summaryTranscriptOf(st) {
16812
17075
  const f = this.#transcriptOf(st, this.#deps.now());
16813
17076
  if (f) return f;
16814
- const homes = st.tool === "workbuddy" ? [st.configDir, dirname8(workbuddySettingsPath()), codebuddyConfigDir()] : st.tool === "qwenwork" ? [qwenworkDataHome()] : [];
17077
+ const homes = st.tool === "workbuddy" ? [st.configDir, dirname9(workbuddySettingsPath()), codebuddyConfigDir()] : st.tool === "qwenwork" ? [qwenworkDataHome()] : [];
16815
17078
  for (const h of homes) {
16816
17079
  if (!h) continue;
16817
17080
  const p = claudeTranscriptFile(st.sessionId, st.cwd, h);
@@ -17131,6 +17394,19 @@ var Daemon = class {
17131
17394
  return;
17132
17395
  }
17133
17396
  }
17397
+ if (cmd.tool === "deepseek_harness" && !sessionId) {
17398
+ let sup = { ok: true };
17399
+ try {
17400
+ sup = this.#deps.dshSupport();
17401
+ } catch {
17402
+ sup = { ok: true };
17403
+ }
17404
+ if (!sup.ok) {
17405
+ 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`);
17406
+ this.#report(cmd, "", "rejected", "", DSH_VERSION_UNSUPPORTED);
17407
+ return;
17408
+ }
17409
+ }
17134
17410
  const runId = newRunId();
17135
17411
  const text = isSummary(cmd) ? withSummaryMark(cmd.text, cmd.instructionId) : cmd.text;
17136
17412
  const plan = buildSpawnPlan({ ...cmd, sessionId, text }, cwd, this.#gateTarget(runId), void 0, sessionId ? readState(sessionId)?.configDir : void 0);
@@ -17167,12 +17443,15 @@ var Daemon = class {
17167
17443
  );
17168
17444
  const writerBusy = cmd.tool === "codex" && isCodexWriterBusy(result, cmd.text);
17169
17445
  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`);
17446
+ const dshOld = cmd.tool === "deepseek_harness" && !sessionId && dshOutputUnsupported(`${result.stderr ?? ""}
17447
+ ${result.stdout}`);
17448
+ 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
17449
  this.#report(
17171
17450
  cmd,
17172
17451
  sessionId || result.sessionId || "",
17173
17452
  "failed",
17174
17453
  "headless",
17175
- notLoggedIn ? AGENT_NOT_LOGGED_IN : writerBusy ? "window_open" : "exec_failed"
17454
+ notLoggedIn ? AGENT_NOT_LOGGED_IN : writerBusy ? "window_open" : dshOld ? DSH_VERSION_UNSUPPORTED : "exec_failed"
17176
17455
  );
17177
17456
  if (notLoggedIn) this.#guideLogin(cmd.tool);
17178
17457
  }
@@ -17407,7 +17686,7 @@ async function runDaemon(deps2 = {}) {
17407
17686
  }
17408
17687
 
17409
17688
  // src/daemon/shimCheck.ts
17410
- import { readFileSync as readFileSync14 } from "fs";
17689
+ import { readFileSync as readFileSync15 } from "fs";
17411
17690
  var SHIM_CHECK_FIRST_DELAY_MS = 60 * 1e3;
17412
17691
  var SHIM_CHECK_INTERVAL_MS = 60 * 60 * 1e3;
17413
17692
  var TOOL = "claude_code";
@@ -17503,7 +17782,7 @@ function defaultShimCheckDeps(log2, idle, inj = {}) {
17503
17782
  idle,
17504
17783
  shimInstalled: () => {
17505
17784
  try {
17506
- return readFileSync14(shimFile(probe.binDir, command, "win32"), "utf8").includes(SHIM_MARKER);
17785
+ return readFileSync15(shimFile(probe.binDir, command, "win32"), "utf8").includes(SHIM_MARKER);
17507
17786
  } catch {
17508
17787
  return false;
17509
17788
  }
@@ -17712,7 +17991,7 @@ async function runDisable(tools, opts = {}) {
17712
17991
 
17713
17992
  // src/commands/install.ts
17714
17993
  import { mkdirSync as mkdirSync13, rmSync as rmSync12, writeFileSync as writeFileSync13 } from "fs";
17715
- import { join as join27 } from "path";
17994
+ import { join as join28 } from "path";
17716
17995
 
17717
17996
  // src/commands/setup.ts
17718
17997
  import qrcode from "qrcode-terminal";
@@ -17929,7 +18208,7 @@ function inSandbox(env) {
17929
18208
  }
17930
18209
  function precheckWritable(dirs) {
17931
18210
  for (const dir2 of dirs) {
17932
- const probe = join27(dir2, `.deskbuddy-probe-${process.pid}`);
18211
+ const probe = join28(dir2, `.deskbuddy-probe-${process.pid}`);
17933
18212
  try {
17934
18213
  mkdirSync13(dir2, { recursive: true });
17935
18214
  writeFileSync13(probe, "");
@@ -18339,11 +18618,12 @@ async function runAutoSwitch(opts) {
18339
18618
  // src/wrap/run.ts
18340
18619
  import { randomBytes as randomBytes10 } from "crypto";
18341
18620
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync15, statSync as statSync16, truncateSync as truncateSync3 } from "fs";
18342
- import { join as join30 } from "path";
18621
+ import { join as join31 } from "path";
18343
18622
  import { StringDecoder as StringDecoder2 } from "string_decoder";
18344
18623
 
18345
18624
  // src/wrap/injector.ts
18346
18625
  var SEEN_MAX = 200;
18626
+ var PERMISSION_PENDING = "permission_pending";
18347
18627
  var WINDOW_UNRECOGNIZED = "window_unrecognized";
18348
18628
  var QUIET_MS = 1500;
18349
18629
  var STARTUP_QUIET_MS = 3e3;
@@ -18509,7 +18789,8 @@ var Injector = class {
18509
18789
  return;
18510
18790
  }
18511
18791
  if (job.expireAt > 0 && this.#d.now() > job.expireAt) {
18512
- this.#final(job, { status: "rejected", mode: "window", reason: "expired" });
18792
+ const perm = this.#lastWhy.split(",").includes("permission");
18793
+ this.#final(job, { status: "rejected", mode: "window", reason: perm ? PERMISSION_PENDING : "expired" });
18513
18794
  return;
18514
18795
  }
18515
18796
  if (!this.#owns(job.sessionId)) {
@@ -18684,9 +18965,9 @@ var WithdrawWatch = class {
18684
18965
 
18685
18966
  // src/wrap/passthrough.ts
18686
18967
  import { spawn as spawn3 } from "child_process";
18687
- import { realpathSync as realpathSync5 } from "fs";
18968
+ import { realpathSync as realpathSync6 } from "fs";
18688
18969
  import { constants } from "os";
18689
- import { dirname as dirname9, resolve as resolve7 } from "path";
18970
+ import { dirname as dirname10, resolve as resolve7 } from "path";
18690
18971
  var CLAUDE_SUBCOMMANDS = /* @__PURE__ */ new Set([
18691
18972
  "agents",
18692
18973
  "attach",
@@ -18742,7 +19023,7 @@ function norm(p, win) {
18742
19023
  }
18743
19024
  function real(p) {
18744
19025
  try {
18745
- return realpathSync5(p);
19026
+ return realpathSync6(p);
18746
19027
  } catch {
18747
19028
  return p;
18748
19029
  }
@@ -18750,7 +19031,7 @@ function real(p) {
18750
19031
  function findReal(tool, d = defaultLocateDeps(), skip = shimDir()) {
18751
19032
  const win = (d.platform ?? process.platform) === "win32";
18752
19033
  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));
19034
+ const inSkip = (file) => skipKeys.has(norm(dirname10(file), win)) || skipKeys.has(norm(dirname10(real(file)), win));
18754
19035
  const dd = { ...d, skipDirs: [...d.skipDirs ?? [], skip] };
18755
19036
  const name = SHIM_COMMANDS[tool];
18756
19037
  if (!name) {
@@ -18834,10 +19115,10 @@ function runDirect(real2, args, env = process.env) {
18834
19115
 
18835
19116
  // src/wrap/codexLaunch.ts
18836
19117
  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";
19118
+ 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
19119
  import { request as request2 } from "http";
18839
19120
  import { createServer as createServer5 } from "net";
18840
- import { join as join28 } from "path";
19121
+ import { join as join29 } from "path";
18841
19122
  var TAKEOVER_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "--config", "-m", "--model", "-s", "--sandbox", "-a", "--ask-for-approval", "-C", "--cd", "--enable", "--disable"]);
18842
19123
  var SHORT_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "-m", "-s", "-a", "-C"]);
18843
19124
  var TAKEOVER_FLAGS = /* @__PURE__ */ new Set(["--search", "--no-alt-screen", "--dangerously-bypass-approvals-and-sandbox"]);
@@ -19006,10 +19287,10 @@ async function runCodexWindow(real2, args, d) {
19006
19287
  }
19007
19288
  var MAX_LOG_BYTES2 = 1024 * 1024;
19008
19289
  function appServerLogPath() {
19009
- return join28(deskbuddyHome(), "codex-app-server.log");
19290
+ return join29(deskbuddyHome(), "codex-app-server.log");
19010
19291
  }
19011
19292
  function windowsDir2() {
19012
- return join28(deskbuddyHome(), "codex-windows");
19293
+ return join29(deskbuddyHome(), "codex-windows");
19013
19294
  }
19014
19295
  function pidAlive5(pid) {
19015
19296
  try {
@@ -19022,7 +19303,7 @@ function pidAlive5(pid) {
19022
19303
  function daemonAlive(pidFile = pidFilePath(), args = processArgs) {
19023
19304
  let pid = 0;
19024
19305
  try {
19025
- pid = Number(readFileSync15(pidFile, "utf8").trim());
19306
+ pid = Number(readFileSync16(pidFile, "utf8").trim());
19026
19307
  } catch {
19027
19308
  return false;
19028
19309
  }
@@ -19099,7 +19380,7 @@ function killOwnServer(pid, url, args = freshArgs, kill = (p) => killServer(p))
19099
19380
  if (a && isAppServerArgs(a, url)) kill(pid);
19100
19381
  }
19101
19382
  function recordPath(rec) {
19102
- return join28(windowsDir2(), `${rec.server}.json`);
19383
+ return join29(windowsDir2(), `${rec.server}.json`);
19103
19384
  }
19104
19385
  function writeRecord(rec) {
19105
19386
  try {
@@ -19124,10 +19405,10 @@ function reapWindows(dir2 = windowsDir2(), alive = pidAlive5, args = freshArgs,
19124
19405
  return;
19125
19406
  }
19126
19407
  for (const n of names) {
19127
- const p = join28(dir2, n);
19408
+ const p = join29(dir2, n);
19128
19409
  let rec;
19129
19410
  try {
19130
- rec = JSON.parse(readFileSync15(p, "utf8"));
19411
+ rec = JSON.parse(readFileSync16(p, "utf8"));
19131
19412
  } catch {
19132
19413
  rmSync14(p, { force: true });
19133
19414
  continue;
@@ -19147,7 +19428,7 @@ function reapWindows(dir2 = windowsDir2(), alive = pidAlive5, args = freshArgs,
19147
19428
  function launchLog(line) {
19148
19429
  try {
19149
19430
  mkdirSync14(deskbuddyHome(), { recursive: true });
19150
- writeFileSync14(join28(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
19431
+ writeFileSync14(join29(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
19151
19432
  `, { flag: "a" });
19152
19433
  } catch {
19153
19434
  }
@@ -19172,7 +19453,7 @@ function defaultCodexWindowDeps(base) {
19172
19453
  // src/wrap/promptLaunch.ts
19173
19454
  import { randomBytes as randomBytes8, randomUUID as randomUUID3 } from "crypto";
19174
19455
  import { homedir as homedir10 } from "os";
19175
- import { join as join29 } from "path";
19456
+ import { join as join30 } from "path";
19176
19457
  var FIRST_LINE_WAIT_MS2 = 2 * 60 * 1e3;
19177
19458
  function promptArgs(text, tool = "") {
19178
19459
  if (!text) return [];
@@ -19182,7 +19463,7 @@ function promptArgs(text, tool = "") {
19182
19463
  function promptEnv(tool, env, launcherPid = process.pid, answerWindowId = "") {
19183
19464
  const out = { ...env };
19184
19465
  if (tool === "workbuddy") {
19185
- if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR = join29(homedir10(), ".workbuddy");
19466
+ if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR = join30(homedir10(), ".workbuddy");
19186
19467
  out.DESKBUDDY_LAUNCHER_PID = String(launcherPid);
19187
19468
  out.DESKBUDDY_LAUNCHED_TOOL = tool;
19188
19469
  if (answerWindowId) out.DESKBUDDY_LAUNCHED_WINDOW = answerWindowId;
@@ -19470,7 +19751,7 @@ async function applyAnswer(req, io, trust) {
19470
19751
  } else if (a.kind === "trust" && a.prompt !== "hooks" && trust?.widens) r = "not_waiting";
19471
19752
  else if (a.kind === "trust") r = await answerTrust(keys, trust?.tool ?? "", a.decision, trust?.forms ?? [], a.prompt ?? "trust");
19472
19753
  else if (a.kind === "permission") r = await answerPermission(keys, a.toolName, a.fingerprint, a.decision);
19473
- else r = await answerQuestions(keys, a.questions, a.selections);
19754
+ else r = await answerQuestions(keys, a.questions, a.selections, a.others);
19474
19755
  } catch {
19475
19756
  r = "partial";
19476
19757
  }
@@ -19721,7 +20002,7 @@ async function runAnswerWindow(tool, real2, args, env, windowId, d) {
19721
20002
  }
19722
20003
 
19723
20004
  // src/wrap/launchRegister.ts
19724
- import { dirname as dirname10 } from "path";
20005
+ import { dirname as dirname11 } from "path";
19725
20006
  async function launchTranscriptReady(tool, sessionId) {
19726
20007
  try {
19727
20008
  if (tool === "qoder_cn") {
@@ -19729,7 +20010,7 @@ async function launchTranscriptReady(tool, sessionId) {
19729
20010
  return !!p && (await transcriptInfo(p)).title !== "";
19730
20011
  }
19731
20012
  if (tool === "workbuddy") {
19732
- const p = findWbTranscript(dirname10(workbuddySettingsPath()), sessionId);
20013
+ const p = findWbTranscript(dirname11(workbuddySettingsPath()), sessionId);
19733
20014
  return !!p && wbTranscriptInfo(p).title !== "";
19734
20015
  }
19735
20016
  return false;
@@ -19737,8 +20018,8 @@ async function launchTranscriptReady(tool, sessionId) {
19737
20018
  return false;
19738
20019
  }
19739
20020
  }
19740
- var defaultDeps2 = { qoder: (j) => syncQoderApp(j), workbuddy: (j) => registerWorkbuddySession(j) };
19741
- async function launchRegister(tool, sessionId, cwd, firstLine, final, d = defaultDeps2) {
20021
+ var defaultDeps3 = { qoder: (j) => syncQoderApp(j), workbuddy: (j) => registerWorkbuddySession(j) };
20022
+ async function launchRegister(tool, sessionId, cwd, firstLine, final, d = defaultDeps3) {
19742
20023
  if (tool === "workbuddy") return d.workbuddy({ sessionId, cwd, fallbackTitle: firstLine ?? "" });
19743
20024
  if (tool !== "qoder_cn") return "skip tool";
19744
20025
  const r = await d.qoder({ tool, kind: "register", sessionId, cwd, fallbackTitle: firstLine ?? "" });
@@ -19766,7 +20047,7 @@ function childEnv(src, windowId, takeover = true) {
19766
20047
  }
19767
20048
  function wrapLog(line) {
19768
20049
  try {
19769
- const p = join30(deskbuddyHome(), "wrap.log");
20050
+ const p = join31(deskbuddyHome(), "wrap.log");
19770
20051
  mkdirSync15(deskbuddyHome(), { recursive: true });
19771
20052
  try {
19772
20053
  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,57 @@
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 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
341
+ 2026-10-12T08:43:33.911Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
342
+ 2026-10-12T08:43:38.975Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
343
+ 2026-10-12T08:43:38.975Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
344
+ 2026-10-12T08:43:39.078Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
345
+ 2026-10-12T08:43:39.078Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
346
+ 2026-10-12T08:43:41.400Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
347
+ 2026-10-12T08:45:36.004Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
348
+ 2026-10-12T08:45:40.986Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
349
+ 2026-10-12T08:45:40.986Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
350
+ 2026-10-12T08:45:41.084Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
351
+ 2026-10-12T08:45:41.084Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
352
+ 2026-10-12T08:45:43.292Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
353
+ 2026-10-12T08:51:55.878Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
354
+ 2026-10-12T08:52:01.359Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
355
+ 2026-10-12T08:52:01.360Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
356
+ 2026-10-12T08:52:01.517Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
357
+ 2026-10-12T08:52:01.517Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
358
+ 2026-10-12T08:52:04.661Z [opencode] 后台任务里的提问 que_r2 没法在设备上回答(后台任务不能提问,请在电脑上继续),已拒绝
359
+ 2026-10-12T09:01:07.164Z [opencode] 回填选择题失败:/question/{requestID}/reply status=404
360
+ 2026-10-12T09:01:12.193Z [opencode] opencode 版本 1.4.3:带说明的拒绝不走新路由
361
+ 2026-10-12T09:01:12.194Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
362
+ 2026-10-12T09:01:12.293Z [opencode] opencode 版本 读不出:带说明的拒绝不走新路由
363
+ 2026-10-12T09:01:12.293Z [opencode] opencode 版本不确定支持带说明的拒绝,设备上的反馈没有带给模型
364
+ 2026-10-12T09:01:14.613Z [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.36",
4
4
  "description": "把 Claude Code、Codex 等 AI 编码智能体每个会话窗口的状态,实时同步到桌面硬件屏幕",
5
5
  "keywords": [
6
6
  "ai",