deskbuddy 0.2.0-beta.43 → 0.2.0-beta.47

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.
@@ -203,7 +203,10 @@ function loadConfig() {
203
203
  cozeApp: typeof parsed.cozeApp === "boolean" ? parsed.cozeApp : void 0,
204
204
  cozeCdpPort: portOrUndefined(parsed.cozeCdpPort),
205
205
  chatgptAppPort: typeof parsed.chatgptAppPort === "boolean" ? parsed.chatgptAppPort : void 0,
206
- chatgptCdpPort: portOrUndefined(parsed.chatgptCdpPort)
206
+ chatgptCdpPort: portOrUndefined(parsed.chatgptCdpPort),
207
+ focusSessionOnRemote: typeof parsed.focusSessionOnRemote === "boolean" ? parsed.focusSessionOnRemote : void 0,
208
+ focusSessionOnRemoteWindows: typeof parsed.focusSessionOnRemoteWindows === "boolean" ? parsed.focusSessionOnRemoteWindows : void 0,
209
+ focusSessionInApp: typeof parsed.focusSessionInApp === "boolean" ? parsed.focusSessionInApp : void 0
207
210
  };
208
211
  } catch {
209
212
  return fallback;
@@ -1433,6 +1436,13 @@ async function refreshRegistry() {
1433
1436
  } catch {
1434
1437
  }
1435
1438
  }
1439
+ var DEFAULT_TITLE_SOURCES = ["git-branch", "project"];
1440
+ function titleSources(registry, tool) {
1441
+ const fromRegistry = registry?.tools?.[tool]?.titleSources;
1442
+ if (fromRegistry?.length) return fromRegistry;
1443
+ const builtin = FALLBACK.tools[tool]?.titleSources;
1444
+ return builtin?.length ? builtin : DEFAULT_TITLE_SOURCES;
1445
+ }
1436
1446
 
1437
1447
  // src/adapters/ccHook.ts
1438
1448
  import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync3, statSync, writeFileSync as writeFileSync6 } from "fs";
@@ -2475,6 +2485,324 @@ function rememberTitle(titles, info) {
2475
2485
  titles.set(info.id, t.slice(0, 100));
2476
2486
  }
2477
2487
 
2488
+ // src/daemon/gate/options.ts
2489
+ import { homedir as homedir3 } from "os";
2490
+
2491
+ // src/daemon/gate/claudeLabels.ts
2492
+ var CC_MERGE_MAX = 8;
2493
+ var CC_LABEL_MAX_WIDTH = 160;
2494
+ var CC_VALUE_MAX = 256;
2495
+ var CC_SUGGESTIONS_MAX = 64;
2496
+ var CC_TEXT = {
2497
+ yes: "Yes",
2498
+ no: "No",
2499
+ noTell: "No, and tell Claude what to do differently",
2500
+ readHead: "Yes, allow reading from ",
2501
+ fromProject: " from this project",
2502
+ duringSession: " during this session",
2503
+ forSession: " for this session",
2504
+ alwaysAccessHead: "Yes, and always allow access to ",
2505
+ accessHead: "Yes, and allow access to ",
2506
+ allowHead: "Yes, and allow ",
2507
+ accessAnd: " access and",
2508
+ and: " and",
2509
+ commands: " commands",
2510
+ dontAskHead: "Yes, and don't ask again for ",
2511
+ /** 可编辑前缀项(单条子命令):标签 + `: ` + 前缀(弯撇号 U+2019) */
2512
+ dontAskPrefix: "Yes, and don\u2019t ask again for: ",
2513
+ switchHead: "Yes, and switch to ",
2514
+ claudeFolderProject: "Yes, and allow Claude to edit files in this project's .claude folder for this session",
2515
+ claudeFolderHome: "Yes, and allow Claude to edit files in its ~/.claude folder for this session",
2516
+ autoMode: "Yes, and switch to auto mode",
2517
+ autoModeNote: "\xB7 auto mode handles these prompts for you"
2518
+ };
2519
+ var CC_MODE_TEXT = {
2520
+ acceptEdits: "accept edits (auto-approve file edits and common file commands)"
2521
+ };
2522
+ function isWide(cp) {
2523
+ return cp >= 4352 && cp <= 4447 || cp >= 11904 && cp <= 12350 || cp >= 12353 && cp <= 13311 || cp >= 13312 && cp <= 19903 || cp >= 19968 && cp <= 40959 || cp >= 40960 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510;
2524
+ }
2525
+ function plainChars(s) {
2526
+ for (const ch of s) {
2527
+ const cp = ch.codePointAt(0);
2528
+ if (cp >= 32 && cp <= 126) continue;
2529
+ if (isWide(cp)) continue;
2530
+ return false;
2531
+ }
2532
+ return true;
2533
+ }
2534
+ function displayWidth(s) {
2535
+ let w = 0;
2536
+ for (const ch of s) w += isWide(ch.codePointAt(0)) ? 2 : 1;
2537
+ return w;
2538
+ }
2539
+ function shownAsIs(s) {
2540
+ return s.length > 0 && s.length <= CC_VALUE_MAX && s.trim() === s && !/\s\s/.test(s) && !s.startsWith('"') && !s.endsWith('"') && plainChars(s);
2541
+ }
2542
+ function listItemAsIs(s) {
2543
+ return shownAsIs(s) && !s.includes(",") && !s.includes(";") && !s.includes(" and ") && !s.startsWith("and ") && !s.endsWith(" and");
2544
+ }
2545
+ function joinAnd(items) {
2546
+ if (items.length <= 2) return items.join(" and ");
2547
+ return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`;
2548
+ }
2549
+ var uniq = (a) => [...new Set(a)];
2550
+ function distinctShown(raw, show) {
2551
+ const seen = /* @__PURE__ */ new Set();
2552
+ for (const r of raw) {
2553
+ const k = show(r).normalize("NFC");
2554
+ if (seen.has(k)) return false;
2555
+ seen.add(k);
2556
+ }
2557
+ return true;
2558
+ }
2559
+ var isObj = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
2560
+ var ruleOut = (r) => ({ toolName: r.toolName, ruleContent: r.ruleContent });
2561
+ var addRulesOut = (rules, destination) => ({ type: "addRules", rules: rules.map(ruleOut), behavior: "allow", destination });
2562
+ var addDirsOut = (directories, destination) => ({ type: "addDirectories", directories, destination });
2563
+ var DESTS = /* @__PURE__ */ new Set(["localSettings", "session"]);
2564
+ function readShown(rc) {
2565
+ if (rc.includes("\\")) return null;
2566
+ const s = rc.replace(/\/\*\*$/, "").replace(/^\.\//, "").replace(/^\/\//, "/");
2567
+ return s ? s : null;
2568
+ }
2569
+ function commandShown(rc) {
2570
+ const s = rc.endsWith(":*") || rc.endsWith(" *") ? rc.slice(0, -2) : rc;
2571
+ if (!s || /[<>]/.test(s)) return null;
2572
+ return s;
2573
+ }
2574
+ function shellApplies(sugg, tool) {
2575
+ if (sugg.length > CC_SUGGESTIONS_MAX) return null;
2576
+ const out = [];
2577
+ for (const s of sugg) {
2578
+ if (!isObj(s)) return null;
2579
+ if (s.type === "addRules") {
2580
+ if (s.behavior !== "allow" || !DESTS.has(s.destination)) continue;
2581
+ if (!Array.isArray(s.rules)) return null;
2582
+ const rules = [];
2583
+ for (const r of s.rules) {
2584
+ if (!isObj(r) || typeof r.toolName !== "string" || r.ruleContent !== void 0 && typeof r.ruleContent !== "string") return null;
2585
+ if (r.toolName === tool) {
2586
+ if (typeof r.ruleContent !== "string" || !r.ruleContent) continue;
2587
+ if (commandShown(r.ruleContent) === null) return null;
2588
+ rules.push(ruleOut(r));
2589
+ } else if (r.toolName === "Read") {
2590
+ if (typeof r.ruleContent !== "string" || !r.ruleContent) continue;
2591
+ if (readShown(r.ruleContent) === null) return null;
2592
+ rules.push(ruleOut(r));
2593
+ }
2594
+ }
2595
+ if (rules.length) out.push(addRulesOut(rules, s.destination));
2596
+ } else if (s.type === "addDirectories") {
2597
+ if (!DESTS.has(s.destination)) continue;
2598
+ if (!Array.isArray(s.directories)) return null;
2599
+ const dirs = s.directories.filter((d) => typeof d === "string" && d.trim() !== "");
2600
+ if (dirs.length !== s.directories.length) return null;
2601
+ if (dirs.length) out.push(addDirsOut(dirs, s.destination));
2602
+ }
2603
+ }
2604
+ return out;
2605
+ }
2606
+ function shellRowLabel(applies, tool, cwdShown) {
2607
+ const rules = applies.filter((s) => s.type === "addRules").flatMap((s) => s.rules);
2608
+ const dirsRaw = applies.filter((s) => s.type === "addDirectories").flatMap((s) => s.directories);
2609
+ if (rules.length + dirsRaw.length > CC_MERGE_MAX) return null;
2610
+ const dirs = uniq(dirsRaw);
2611
+ const reads = uniq(rules.filter((r) => r.toolName === "Read").map((r) => r.ruleContent));
2612
+ const cmds = uniq(rules.filter((r) => r.toolName === tool).map((r) => r.ruleContent));
2613
+ const readShow = (r) => readShown(r);
2614
+ const cmdShow = (r) => commandShown(r);
2615
+ const pathRaw = [...reads, ...dirs];
2616
+ const pathShow = (r) => reads.includes(r) ? readShow(r) : r;
2617
+ if (!distinctShown(pathRaw, pathShow) || !distinctShown(cmds, cmdShow)) return null;
2618
+ const R = cmds.map(cmdShow);
2619
+ const E = reads.map(readShow);
2620
+ const K = dirs;
2621
+ if (![...R, ...E, ...K].every(listItemAsIs)) return null;
2622
+ if (displayWidth([...R, ...E, ...K].join(" and ")) > CC_LABEL_MAX_WIDTH) return null;
2623
+ const hasDir = K.length > 0;
2624
+ const hasRead = E.length > 0;
2625
+ const hasCmd = R.length > 0;
2626
+ if (hasRead && !hasDir && !hasCmd) return CC_TEXT.readHead + joinAnd(E) + CC_TEXT.fromProject;
2627
+ if (!hasCmd && (hasDir || hasRead)) return CC_TEXT.alwaysAccessHead + joinAnd([...K, ...E]) + CC_TEXT.fromProject;
2628
+ if (hasCmd && !hasDir && !hasRead) {
2629
+ if (cwdShown === null) return null;
2630
+ return CC_TEXT.dontAskHead + joinAnd(R) + CC_TEXT.commands + " in " + cwdShown;
2631
+ }
2632
+ if (hasCmd) {
2633
+ const paths = [...K, ...E];
2634
+ if (paths.length === 1 && R.length === 1) return CC_TEXT.accessHead + paths[0] + CC_TEXT.and + " " + R[0] + CC_TEXT.commands;
2635
+ return CC_TEXT.allowHead + joinAnd(paths) + CC_TEXT.accessAnd + " " + joinAnd(R) + CC_TEXT.commands;
2636
+ }
2637
+ return null;
2638
+ }
2639
+ function projectSlug(p) {
2640
+ return p.replace(/[^a-zA-Z0-9]/g, "-");
2641
+ }
2642
+ function sessionRoot(cwd, transcriptPath) {
2643
+ if (typeof cwd !== "string" || !cwd || typeof transcriptPath !== "string" || !transcriptPath) return null;
2644
+ if (!shownAsIs(cwd) || /[\u0000-\u001f]/.test(cwd)) return null;
2645
+ const parts = transcriptPath.split(/[\\/]/);
2646
+ const dir2 = parts.length >= 2 ? parts[parts.length - 2] : "";
2647
+ const slug = projectSlug(cwd);
2648
+ if (slug.length <= 200 ? dir2 !== slug : !dir2.startsWith(`${slug.slice(0, 200)}-`)) return null;
2649
+ return cwd;
2650
+ }
2651
+ var CC_AUTO_LABEL = `${CC_TEXT.autoMode} ${CC_TEXT.autoModeNote}`;
2652
+ var CC_AUTO_DESC = "\u5141\u8BB8\u8FD9\u4E00\u6B21\uFF0C\u5E76\u628A\u6574\u4E2A\u4F1A\u8BDD\u5207\u5230\u81EA\u52A8\u6A21\u5F0F\uFF1A\u4E4B\u540E\u8FD9\u7C7B\u786E\u8BA4\u7531 Claude \u81EA\u5DF1\u5224\u65AD\u653E\u884C\uFF0C\u4E0D\u518D\u9010\u6761\u95EE\u4F60";
2653
+ var CC_AUTO_MODE_UPDATE = { type: "setMode", mode: "auto", destination: "session" };
2654
+ function yesRowNo(middle, action, opts = {}) {
2655
+ const options = [{ id: "allow", label: CC_TEXT.yes, kind: "allow" }];
2656
+ const actions = { allow: { decision: "allow" } };
2657
+ if (middle && action) {
2658
+ options.push(middle);
2659
+ actions[middle.id] = action;
2660
+ }
2661
+ if (opts.auto) {
2662
+ options.push({ id: "auto", label: CC_AUTO_LABEL, description: CC_AUTO_DESC, kind: "allow_session" });
2663
+ actions.auto = { decision: "allow", updatedPermissions: [{ ...CC_AUTO_MODE_UPDATE }] };
2664
+ }
2665
+ if (opts.noTell) options.push({ id: "deny", label: CC_TEXT.noTell, kind: "deny" });
2666
+ else options.push({ id: "deny", label: CC_TEXT.no, description: CC_TEXT.noTell, kind: "deny", input: true });
2667
+ actions.deny = { decision: "deny" };
2668
+ return { options, actions };
2669
+ }
2670
+ function claudeShellLabels(tool, sugg, auto, ctx = {}) {
2671
+ if (isObj(ctx.toolInput) && "_host" in ctx.toolInput) return null;
2672
+ const applies = shellApplies(sugg, tool);
2673
+ if (applies === null || applies.length === 0) return null;
2674
+ const mixed = applies.some((s) => s.type === "addDirectories" || s.rules?.some((r) => r.toolName !== tool));
2675
+ const cmds = uniq(applies.flatMap((s) => s.type === "addRules" ? s.rules : []).filter((r) => r.toolName === tool).map((r) => r.ruleContent));
2676
+ const autoOpt = tool === "Bash" && auto;
2677
+ if (mixed) {
2678
+ const label3 = shellRowLabel(applies, tool, null);
2679
+ if (label3 === null) return null;
2680
+ const kind2 = applies.some((s) => s.destination === "localSettings") ? "allow_always" : "allow_session";
2681
+ const id = cmds.length ? "always" : applies.some((s) => s.type === "addDirectories") ? "dir" : "read";
2682
+ return yesRowNo({ id, label: label3, kind: kind2 }, { decision: "allow", updatedPermissions: applies }, { auto: autoOpt });
2683
+ }
2684
+ if (tool === "PowerShell") return null;
2685
+ if (cmds.length === 1) {
2686
+ if (sugg.length !== 1) return null;
2687
+ const raw = sugg[0];
2688
+ if (!Array.isArray(raw.rules) || raw.rules.length !== 1) return null;
2689
+ const s = applies[0];
2690
+ const rules = s.rules;
2691
+ if (s.destination !== "localSettings" || rules.length !== 1) return null;
2692
+ const rc = rules[0].ruleContent;
2693
+ if (!isObj(raw.rules[0]) || raw.rules[0].toolName !== tool || raw.rules[0].ruleContent !== rc) return null;
2694
+ if (!rc.endsWith(" *") || rc.trim() === "*") return null;
2695
+ return yesRowNo(
2696
+ { id: "always", label: CC_TEXT.dontAskPrefix + rc, kind: "allow_always" },
2697
+ { decision: "allow", updatedPermissions: [addRulesOut([{ toolName: tool, ruleContent: rc }], "localSettings")] },
2698
+ { auto: autoOpt }
2699
+ );
2700
+ }
2701
+ const root = sessionRoot(ctx.cwd, ctx.transcriptPath);
2702
+ if (root === null) return null;
2703
+ const label2 = shellRowLabel(applies, tool, root);
2704
+ if (label2 === null) return null;
2705
+ const kind = applies.some((s) => s.destination === "localSettings") ? "allow_always" : "allow_session";
2706
+ return yesRowNo({ id: "always", label: label2, kind }, { decision: "allow", updatedPermissions: applies }, { auto: autoOpt });
2707
+ }
2708
+ function underDir(file3, dir2, platform) {
2709
+ const fold = platform === "darwin" || platform === "win32" ? (s) => s.toLowerCase() : (s) => s;
2710
+ return fold(file3).startsWith(fold(dir2.replace(/\/+$/, "")) + "/");
2711
+ }
2712
+ function claudeFileLabels(toolName, sugg, ctx = {}) {
2713
+ const read2 = toolName === "Read";
2714
+ if (read2 && ctx.permissionMode === "auto") return null;
2715
+ if (sugg.length === 0 || sugg.length > CC_SUGGESTIONS_MAX || !sugg.every(isObj)) return null;
2716
+ const sg = sugg;
2717
+ const file3 = isObj(ctx.toolInput) ? ctx.toolInput.file_path ?? ctx.toolInput.notebook_path : void 0;
2718
+ if (!read2 && typeof file3 === "string") {
2719
+ const platform = ctx.platform ?? process.platform;
2720
+ const home = ctx.home;
2721
+ const root = sessionRoot(ctx.cwd, ctx.transcriptPath);
2722
+ const absPosix = file3.startsWith("/") && !file3.split("/").includes("..");
2723
+ const inHome = !!home && absPosix && underDir(file3, `${home}/.claude`, platform);
2724
+ const inProject = !!root && absPosix && underDir(file3, `${root}/.claude`, platform);
2725
+ const claudeSegment = /(^|[\\/])\.claude([\\/]|$)/i.test(file3);
2726
+ if (claudeSegment && !inHome && !inProject) return null;
2727
+ if (inHome || inProject) {
2728
+ const content = inHome ? "~/.claude/**" : "/.claude/**";
2729
+ const want = { type: "addRules", rules: [{ toolName: "Edit", ruleContent: content }], behavior: "allow", destination: "session" };
2730
+ const s = sg.length === 1 ? sg[0] : null;
2731
+ const r = s && Array.isArray(s.rules) && s.rules.length === 1 && isObj(s.rules[0]) ? s.rules[0] : null;
2732
+ if (!s || s.type !== "addRules" || s.behavior !== "allow" || s.destination !== "session" || !r || r.toolName !== "Edit" || r.ruleContent !== content) return null;
2733
+ return yesRowNo(
2734
+ { id: "session", label: inHome ? CC_TEXT.claudeFolderHome : CC_TEXT.claudeFolderProject, kind: "allow_session" },
2735
+ { decision: "allow", updatedPermissions: [want] }
2736
+ );
2737
+ }
2738
+ }
2739
+ const rows = [];
2740
+ for (const s of sg) {
2741
+ if (s.destination !== "session") return null;
2742
+ if (s.type === "setMode") {
2743
+ if (read2 || s.mode !== "acceptEdits") return null;
2744
+ } else if (s.type === "addDirectories") {
2745
+ if (read2) return null;
2746
+ } else if (s.type === "addRules") {
2747
+ if (s.behavior !== "allow") return null;
2748
+ } else return null;
2749
+ }
2750
+ if (sg.filter((s) => s.type === "setMode").length > 1 || sg.filter((s) => s.type === "addDirectories").length > 1) return null;
2751
+ if (sg.some((s) => s.type === "setMode")) {
2752
+ rows.push({ label: CC_TEXT.switchHead + CC_MODE_TEXT.acceptEdits + CC_TEXT.forSession, applies: [{ type: "setMode", destination: "session", mode: "acceptEdits" }] });
2753
+ }
2754
+ const dirSugg = sg.find((s) => s.type === "addDirectories");
2755
+ if (dirSugg) {
2756
+ const dirsIn = Array.isArray(dirSugg.directories) ? dirSugg.directories : null;
2757
+ if (!dirsIn || dirsIn.some((d) => typeof d !== "string" || d.trim() === "")) return null;
2758
+ const dirs = uniq(dirsIn);
2759
+ if (dirs.length === 0 || !dirs.every(listItemAsIs) || !distinctShown(dirs, (d) => d)) return null;
2760
+ if (dirs.length <= CC_MERGE_MAX && displayWidth(dirs.join(", ")) <= CC_LABEL_MAX_WIDTH)
2761
+ rows.push({ label: CC_TEXT.alwaysAccessHead + dirs.join(", ") + CC_TEXT.forSession, applies: [{ type: "addDirectories", destination: "session", directories: dirs }] });
2762
+ }
2763
+ const ruleSuggs = sg.filter((s) => s.type === "addRules");
2764
+ if (ruleSuggs.length) {
2765
+ const applies = [];
2766
+ const reads = [];
2767
+ for (const s of ruleSuggs) {
2768
+ if (!Array.isArray(s.rules)) return null;
2769
+ const keep = [];
2770
+ for (const r of s.rules) {
2771
+ if (!isObj(r) || r.toolName !== "Read" || typeof r.ruleContent !== "string" || readShown(r.ruleContent) === null) return null;
2772
+ keep.push(ruleOut(r));
2773
+ reads.push(r.ruleContent);
2774
+ }
2775
+ if (keep.length) applies.push(addRulesOut(keep, s.destination));
2776
+ }
2777
+ if (reads.length === 0) return null;
2778
+ const u = uniq(reads);
2779
+ const shown = u.map((r) => readShown(r));
2780
+ if (!distinctShown(u, (r) => readShown(r)) || !shown.every(listItemAsIs)) return null;
2781
+ if (reads.length <= CC_MERGE_MAX && displayWidth(shown.join(" and ")) <= CC_LABEL_MAX_WIDTH)
2782
+ rows.push({ label: CC_TEXT.readHead + joinAnd(shown) + CC_TEXT.duringSession, applies });
2783
+ }
2784
+ if (rows.length === 0) return null;
2785
+ const label2 = rows.map((r) => r.label).join("; ");
2786
+ return yesRowNo({ id: "session", label: label2, kind: "allow_session" }, { decision: "allow", updatedPermissions: rows.flatMap((r) => r.applies) });
2787
+ }
2788
+ function claudeWebFetchLabels(sugg, ctx = {}) {
2789
+ if (sugg.length !== 1 || !isObj(sugg[0])) return null;
2790
+ const s = sugg[0];
2791
+ if (s.type !== "addRules" || s.behavior !== "allow" || s.destination !== "localSettings" || !Array.isArray(s.rules) || s.rules.length !== 1) return null;
2792
+ const r = s.rules[0];
2793
+ if (!isObj(r) || r.toolName !== "WebFetch" || typeof r.ruleContent !== "string" || !r.ruleContent.startsWith("domain:")) return null;
2794
+ const host = r.ruleContent.slice("domain:".length);
2795
+ const url = isObj(ctx.toolInput) ? ctx.toolInput.url : void 0;
2796
+ let urlHost = null;
2797
+ try {
2798
+ urlHost = typeof url === "string" ? new URL(url).hostname : null;
2799
+ } catch {
2800
+ urlHost = null;
2801
+ }
2802
+ if (!host || host !== urlHost || host.includes("*") || !shownAsIs(host)) return null;
2803
+ return yesRowNo({ id: "always", label: CC_TEXT.dontAskHead + host, kind: "allow_always" }, { decision: "allow", updatedPermissions: [addRulesOut([{ toolName: "WebFetch", ruleContent: r.ruleContent }], "localSettings")] }, { noTell: true });
2804
+ }
2805
+
2478
2806
  // src/daemon/gate/options.ts
2479
2807
  var OPTIONS_MAX = 16;
2480
2808
  var OPTION_LABEL_MAX = 2e3;
@@ -2514,95 +2842,40 @@ function checkOptions(set, limits = {}) {
2514
2842
  return { options: out, actions };
2515
2843
  }
2516
2844
  var CLAUDE_TEXT = {
2517
- yes: "Yes",
2518
- no: "No",
2519
- noInput: "No, and tell Claude what to do differently",
2845
+ yes: CC_TEXT.yes,
2846
+ no: CC_TEXT.no,
2847
+ noInput: CC_TEXT.noTell,
2520
2848
  /** 单条 Bash 前缀规则:`Yes, and don’t ask again for: <规则>`(注意是弯撇号 U+2019) */
2521
- dontAskPrefix: "Yes, and don\u2019t ask again for: ",
2849
+ dontAskPrefix: CC_TEXT.dontAskPrefix,
2522
2850
  /** 编辑类 setMode acceptEdits:整句作 label(101 字,用户拍板不拆;不含快捷键提示 `(shift+tab)`) */
2523
- acceptEdits: "Yes, and switch to accept edits (auto-approve file edits and common file commands) for this session",
2851
+ acceptEdits: CC_TEXT.switchHead + CC_MODE_TEXT.acceptEdits + CC_TEXT.forSession,
2524
2852
  /** Bash 的切 auto 模式(只在默认 / acceptEdits 模式下出现;非 workflow 来源时界面上带 ` · auto mode handles these prompts for you`) */
2525
- autoMode: "Yes, and switch to auto mode \xB7 auto mode handles these prompts for you",
2853
+ autoMode: CC_AUTO_LABEL,
2526
2854
  /** 设备上给「切 auto 模式」的说明(问题表 272:它会把整个会话切过去,要写清楚) */
2527
- autoModeDesc: "\u5141\u8BB8\u8FD9\u4E00\u6B21\uFF0C\u5E76\u628A\u6574\u4E2A\u4F1A\u8BDD\u5207\u5230\u81EA\u52A8\u6A21\u5F0F\uFF1A\u4E4B\u540E\u8FD9\u7C7B\u786E\u8BA4\u7531 Claude \u81EA\u5DF1\u5224\u65AD\u653E\u884C\uFF0C\u4E0D\u518D\u9010\u6761\u95EE\u4F60",
2528
- /**
2529
- * Bash 碰到工作区外的目录(2.1.295 实测,问题表 272):建议是 addDirectories(session) + setMode acceptEdits(session),
2530
- * 界面合成一项 `Yes, and always allow access to <目录> from this project`,选中两条一起生效
2531
- */
2532
- dirAccessHead: "Yes, and always allow access to ",
2533
- dirAccessTail: " from this project"
2855
+ autoModeDesc: CC_AUTO_DESC,
2856
+ /** Bash 碰到工作区外的目录(2.1.295 实测,问题表 272):`Yes, and always allow access to <目录> from this project` */
2857
+ dirAccessHead: CC_TEXT.alwaysAccessHead,
2858
+ dirAccessTail: CC_TEXT.fromProject,
2859
+ /** Bash 读工作区外的路径(问题表 278,2.1.295 实测 `ls /tmp | wc -l`):`Yes, allow reading from <目录> from this project` */
2860
+ readAccessHead: CC_TEXT.readHead
2534
2861
  };
2535
- var CLAUDE_AUTO_MODE_UPDATE = { type: "setMode", mode: "auto", destination: "session" };
2536
- var CLAUDE_EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
2537
- function claudeYesNo(middle, action, auto = false) {
2538
- const options = [{ id: "allow", label: CLAUDE_TEXT.yes, kind: "allow" }];
2539
- const actions = { allow: { decision: "allow" } };
2540
- if (middle && action) {
2541
- options.push(middle);
2542
- actions[middle.id] = action;
2543
- }
2544
- if (auto) {
2545
- options.push({ id: "auto", label: CLAUDE_TEXT.autoMode, description: CLAUDE_TEXT.autoModeDesc, kind: "allow_session" });
2546
- actions.auto = { decision: "allow", updatedPermissions: [{ ...CLAUDE_AUTO_MODE_UPDATE }] };
2547
- }
2548
- options.push({ id: "deny", label: CLAUDE_TEXT.no, description: CLAUDE_TEXT.noInput, kind: "deny", input: true });
2549
- actions.deny = { decision: "deny" };
2550
- return { options, actions };
2551
- }
2552
- function claudeDirAccess(sugg) {
2553
- if (sugg.length < 1 || sugg.length > 2) return null;
2554
- let dir2 = null;
2555
- let mode = 0;
2556
- for (const x of sugg) {
2557
- const s = x;
2558
- if (s && s.type === "addDirectories" && s.destination === "session" && Array.isArray(s.directories) && s.directories.length === 1 && typeof s.directories[0] === "string" && s.directories[0]) {
2559
- if (dir2 !== null) return null;
2560
- dir2 = s.directories[0];
2561
- } else if (s && s.type === "setMode" && s.mode === "acceptEdits" && s.destination === "session") mode++;
2562
- else return null;
2563
- }
2564
- if (!dir2 || mode > 1 || /[\u0000-\u001f\u007f]/.test(dir2)) return null;
2565
- return dir2;
2566
- }
2862
+ var CLAUDE_FILE_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit", "Read"]);
2567
2863
  function claudeOptions(toolName, suggestions, ctx = {}) {
2568
- const sugg = suggestions === void 0 || suggestions === null ? [] : suggestions;
2569
- if (!Array.isArray(sugg)) return null;
2570
- if (toolName === "Bash") {
2571
- const auto = ctx.autoModeAllowed === true && (ctx.permissionMode === "default" || ctx.permissionMode === "acceptEdits");
2572
- if (sugg.length === 0) return checkOptions(claudeYesNo(null, void 0, auto));
2573
- const dir2 = claudeDirAccess(sugg);
2574
- if (dir2) {
2575
- return checkOptions(
2576
- claudeYesNo(
2577
- { id: "dir", label: CLAUDE_TEXT.dirAccessHead + dir2 + CLAUDE_TEXT.dirAccessTail, kind: "allow_session" },
2578
- { decision: "allow", updatedPermissions: sugg },
2579
- auto
2580
- )
2581
- );
2582
- }
2583
- if (sugg.length !== 1) return null;
2584
- const s = sugg[0];
2585
- if (!s || s.type !== "addRules" || s.behavior !== "allow" || s.destination !== "localSettings") return null;
2586
- const rules = Array.isArray(s.rules) ? s.rules : [];
2587
- if (rules.length !== 1 || rules[0]?.toolName !== "Bash") return null;
2588
- const rc = rules[0].ruleContent;
2589
- if (typeof rc !== "string" || !rc.endsWith(" *") || rc.trim() === "*") return null;
2590
- return checkOptions(
2591
- claudeYesNo({ id: "always", label: CLAUDE_TEXT.dontAskPrefix + rc, kind: "allow_always" }, { decision: "allow", updatedPermissions: [s] }, auto)
2592
- );
2593
- }
2594
- if (CLAUDE_EDIT_TOOLS.has(toolName)) {
2595
- if (sugg.length !== 1) return null;
2596
- const s = sugg[0];
2597
- if (!s || s.type !== "setMode" || s.mode !== "acceptEdits" || s.destination !== "session") return null;
2598
- return checkOptions(
2599
- claudeYesNo(
2600
- { id: "session", label: CLAUDE_TEXT.acceptEdits, kind: "allow_session" },
2601
- { decision: "allow", updatedPermissions: [s] }
2602
- )
2603
- );
2864
+ const fallback = (auto) => {
2865
+ const set = checkOptions(yesRowNo(null, null, { auto }));
2866
+ return set && Array.isArray(suggestions) && suggestions.length > 0 ? { ...set, pcAllowAmbiguous: true } : set;
2867
+ };
2868
+ const lctx = { cwd: ctx.cwd, toolInput: ctx.toolInput, transcriptPath: ctx.transcriptPath, permissionMode: ctx.permissionMode, home: ctx.home ?? homedir3(), platform: ctx.platform };
2869
+ if (toolName === "Bash" || toolName === "PowerShell") {
2870
+ const auto = toolName === "Bash" && Array.isArray(suggestions) && ctx.autoModeAllowed === true && (ctx.permissionMode === "default" || ctx.permissionMode === "acceptEdits");
2871
+ if (!Array.isArray(suggestions)) return fallback(false);
2872
+ if (suggestions.length === 0) return fallback(auto);
2873
+ return checkOptions(claudeShellLabels(toolName, suggestions, auto, lctx)) ?? fallback(auto);
2604
2874
  }
2605
- return null;
2875
+ const sugg = Array.isArray(suggestions) ? suggestions : [];
2876
+ if (CLAUDE_FILE_TOOLS.has(toolName)) return checkOptions(claudeFileLabels(toolName, sugg, lctx)) ?? fallback(false);
2877
+ if (toolName === "WebFetch") return checkOptions(claudeWebFetchLabels(sugg, lctx)) ?? fallback(false);
2878
+ return fallback(false);
2606
2879
  }
2607
2880
  var CODEX_TEXT = {
2608
2881
  mcpAllow: "Allow",
@@ -3747,7 +4020,7 @@ function resolveAlias(sessionId, now = Date.now()) {
3747
4020
  // src/wrap/shim.ts
3748
4021
  import { execFile as execFile3, execFileSync as execFileSync7 } from "child_process";
3749
4022
  import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as mkdirSync16, readdirSync as readdirSync6, readFileSync as readFileSync17, rmdirSync, rmSync as rmSync9, writeFileSync as writeFileSync16 } from "fs";
3750
- import { homedir as homedir3 } from "os";
4023
+ import { homedir as homedir4 } from "os";
3751
4024
  import { delimiter, join as join15, resolve as resolve4 } from "path";
3752
4025
  var SHIM_FALLBACK_EXIT = 213;
3753
4026
  var WIN_SHIM_ENABLED = true;
@@ -4073,7 +4346,7 @@ function psWinWhere(exec = execFileSync7) {
4073
4346
  function defaultShimEnv(nodePath, cliPath) {
4074
4347
  const env = process.env;
4075
4348
  const isolated = !!env.VITEST || !!env.DESKBUDDY_HOME;
4076
- const rcHome = env.DESKBUDDY_RC_HOME || (isolated ? null : homedir3());
4349
+ const rcHome = env.DESKBUDDY_RC_HOME || (isolated ? null : homedir4());
4077
4350
  return {
4078
4351
  platform: process.platform,
4079
4352
  binDir: shimDir(),
@@ -4497,11 +4770,11 @@ function launchPending(runId) {
4497
4770
  // src/core/title.ts
4498
4771
  import { closeSync as closeSync3, existsSync as existsSync15, openSync as openSync3, readFileSync as readFileSync20, readSync as readSync3, statSync as statSync7 } from "fs";
4499
4772
  import { createRequire as createRequire2 } from "module";
4500
- import { homedir as homedir5 } from "os";
4773
+ import { homedir as homedir6 } from "os";
4501
4774
  import { basename as basename3, dirname as dirname10, isAbsolute, join as join18 } from "path";
4502
4775
 
4503
4776
  // src/core/dirFilter.ts
4504
- import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
4777
+ import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
4505
4778
  import { posix, win32 as win323 } from "path";
4506
4779
  function norm(p, win) {
4507
4780
  if (win) return win323.resolve(p).replace(/[\\/]+$/, "").toLowerCase();
@@ -4514,7 +4787,7 @@ function isAutoWorkspace(p, opts = {}) {
4514
4787
  if (!p) return false;
4515
4788
  const win = (opts.platform ?? process.platform) === "win32";
4516
4789
  const P = win ? win323 : posix;
4517
- const home = norm(opts.home ?? homedir4(), win);
4790
+ const home = norm(opts.home ?? homedir5(), win);
4518
4791
  const full = norm(p, win);
4519
4792
  const name = P.basename(full);
4520
4793
  const parent = P.dirname(full);
@@ -4527,7 +4800,7 @@ function isReportableDir(p, opts = {}) {
4527
4800
  const P = win ? win323 : posix;
4528
4801
  const full = norm(p, win);
4529
4802
  if (full === norm(P.parse(P.resolve(p)).root, win)) return false;
4530
- if (full === norm(opts.home ?? homedir4(), win)) return false;
4803
+ if (full === norm(opts.home ?? homedir5(), win)) return false;
4531
4804
  const tmps = [opts.tmp ?? tmpdir2()];
4532
4805
  if (!win) tmps.push("/tmp", "/private/tmp", "/var/folders", "/private/var/folders");
4533
4806
  for (const t of tmps) if (t && isUnder(full, norm(t, win), win)) return false;
@@ -4536,6 +4809,7 @@ function isReportableDir(p, opts = {}) {
4536
4809
 
4537
4810
  // src/core/title.ts
4538
4811
  var TRANSCRIPT_TAIL_BYTES = 64 * 1024;
4812
+ var GIT_LOOKUP_MAX_UP = 5;
4539
4813
  var SESSION_TITLE_MAX = 100;
4540
4814
  function cleanTitle(text) {
4541
4815
  if (typeof text !== "string") return "";
@@ -4543,9 +4817,21 @@ function cleanTitle(text) {
4543
4817
  return Array.from(flat).slice(0, SESSION_TITLE_MAX).join("").trimEnd();
4544
4818
  }
4545
4819
  var requireHere = createRequire2(import.meta.url);
4820
+ function loadNodeSqlite() {
4821
+ const orig = process.emitWarning;
4822
+ process.emitWarning = function(...args) {
4823
+ if (String(args[0] ?? "").includes("SQLite")) return;
4824
+ return orig.apply(process, args);
4825
+ };
4826
+ try {
4827
+ return requireHere("node:sqlite");
4828
+ } finally {
4829
+ process.emitWarning = orig;
4830
+ }
4831
+ }
4546
4832
  var QODER_DATA_DIRS = { qoder_cn: "com.qodercn.app.stable", qoder: "com.qoder.app.stable" };
4547
4833
  var QODER_APP_DIR_ENV = { qoder_cn: "DESKBUDDY_QODER_CN_APP_DIR", qoder: "DESKBUDDY_QODER_APP_DIR" };
4548
- function qoderDbPaths(tool, platform = process.platform, home = homedir5(), env = process.env) {
4834
+ function qoderDbPaths(tool, platform = process.platform, home = homedir6(), env = process.env) {
4549
4835
  let root;
4550
4836
  if (platform === "darwin") root = join18(home, "Library", "Application Support");
4551
4837
  else if (platform === "win32") root = env.APPDATA || join18(home, "AppData", "Roaming");
@@ -4554,11 +4840,157 @@ function qoderDbPaths(tool, platform = process.platform, home = homedir5(), env
4554
4840
  const tools = one ? [tool] : ["qoder_cn", "qoder"];
4555
4841
  return tools.map((t) => join18(env[QODER_APP_DIR_ENV[t] ?? ""] || join18(root, QODER_DATA_DIRS[t] ?? ""), "main.sqlite"));
4556
4842
  }
4843
+ function readQoderSessionTitle(sessionId, paths = qoderDbPaths(null), load = loadNodeSqlite) {
4844
+ if (!sessionId) return null;
4845
+ const existing = paths.filter((p) => existsSync15(p));
4846
+ if (existing.length === 0) return null;
4847
+ let mod;
4848
+ try {
4849
+ mod = load();
4850
+ } catch {
4851
+ return null;
4852
+ }
4853
+ for (const p of existing) {
4854
+ let db;
4855
+ try {
4856
+ db = new mod.DatabaseSync(p, { readOnly: true });
4857
+ const row = db.prepare("select title from chat_sessions where session_id = ?").get(sessionId);
4858
+ const t = cleanTitle(row?.title);
4859
+ if (t) return t;
4860
+ } catch {
4861
+ } finally {
4862
+ try {
4863
+ db?.close();
4864
+ } catch {
4865
+ }
4866
+ }
4867
+ }
4868
+ return null;
4869
+ }
4870
+ function readTailLines(path, bytes) {
4871
+ let fd;
4872
+ try {
4873
+ const size = statSync7(path).size;
4874
+ if (size <= 0) return [];
4875
+ const len = Math.min(size, bytes);
4876
+ const buf = Buffer.alloc(len);
4877
+ fd = openSync3(path, "r");
4878
+ readSync3(fd, buf, 0, len, size - len);
4879
+ return buf.toString("utf8").split("\n");
4880
+ } catch {
4881
+ return [];
4882
+ } finally {
4883
+ if (fd !== void 0) {
4884
+ try {
4885
+ closeSync3(fd);
4886
+ } catch {
4887
+ }
4888
+ }
4889
+ }
4890
+ }
4891
+ function readAiTitle(transcriptPath) {
4892
+ if (!transcriptPath) return null;
4893
+ const lines = readTailLines(transcriptPath, TRANSCRIPT_TAIL_BYTES);
4894
+ for (let i = lines.length - 1; i >= 0; i--) {
4895
+ const line = lines[i];
4896
+ if (!line.includes('"ai-title"')) continue;
4897
+ try {
4898
+ const obj = JSON.parse(line);
4899
+ if (obj?.type === "ai-title") {
4900
+ const title = String(obj.aiTitle ?? "").trim();
4901
+ if (title !== "") return title;
4902
+ }
4903
+ } catch {
4904
+ }
4905
+ }
4906
+ return null;
4907
+ }
4908
+ function readCodexThreadName(sessionId, home) {
4909
+ if (!sessionId) return null;
4910
+ const indexPath = join18(home || codexHome(), "session_index.jsonl");
4911
+ const lines = readTailLines(indexPath, TRANSCRIPT_TAIL_BYTES);
4912
+ for (let i = lines.length - 1; i >= 0; i--) {
4913
+ const line = lines[i];
4914
+ if (!line.includes(sessionId)) continue;
4915
+ try {
4916
+ const obj = JSON.parse(line);
4917
+ if (obj?.id === sessionId) {
4918
+ const name = String(obj.thread_name ?? "").trim();
4919
+ if (name !== "") return name;
4920
+ }
4921
+ } catch {
4922
+ }
4923
+ }
4924
+ return null;
4925
+ }
4926
+ function readGitHead(dir2) {
4927
+ try {
4928
+ const dotGit = join18(dir2, ".git");
4929
+ let headPath = join18(dotGit, "HEAD");
4930
+ if (!statSync7(dotGit).isDirectory()) {
4931
+ const m = /^gitdir:\s*(.+)$/m.exec(readFileSync20(dotGit, "utf8"));
4932
+ if (!m) return null;
4933
+ const gitdir = m[1].trim();
4934
+ headPath = join18(isAbsolute(gitdir) ? gitdir : join18(dir2, gitdir), "HEAD");
4935
+ }
4936
+ return readFileSync20(headPath, "utf8").trim();
4937
+ } catch {
4938
+ return null;
4939
+ }
4940
+ }
4941
+ function readGitBranch(cwd, maxUp = GIT_LOOKUP_MAX_UP) {
4942
+ if (!cwd) return null;
4943
+ let dir2 = cwd;
4944
+ for (let up = 0; up <= maxUp; up++) {
4945
+ const head = readGitHead(dir2);
4946
+ if (head !== null) {
4947
+ const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
4948
+ if (!ref) return null;
4949
+ return ref[1].trim() || null;
4950
+ }
4951
+ const parent = dirname10(dir2);
4952
+ if (parent === dir2) break;
4953
+ dir2 = parent;
4954
+ }
4955
+ return null;
4956
+ }
4957
+ function resolveTitle(sources, ctx) {
4958
+ for (const source of sources ?? []) {
4959
+ let value = null;
4960
+ switch (source) {
4961
+ case "cc-ai-title":
4962
+ value = readAiTitle(ctx.transcriptPath);
4963
+ break;
4964
+ case "codex-thread-name":
4965
+ value = readCodexThreadName(ctx.sessionId, ctx.codexHome);
4966
+ break;
4967
+ case "git-branch":
4968
+ value = readGitBranch(ctx.cwd);
4969
+ break;
4970
+ case "first-prompt":
4971
+ value = ctx.firstPrompt || null;
4972
+ break;
4973
+ case "qoder-session-title":
4974
+ value = readQoderSessionTitle(ctx.sessionId, ctx.qoderDbPaths ?? qoderDbPaths(ctx.tool), ctx.loadSqlite);
4975
+ break;
4976
+ case "oc-session-title":
4977
+ value = typeof ctx.ocSessionTitle === "string" ? ctx.ocSessionTitle.trim() || null : null;
4978
+ break;
4979
+ case "project":
4980
+ value = ctx.cwd && !isAutoWorkspace(ctx.cwd) ? basename3(ctx.cwd) || null : null;
4981
+ break;
4982
+ default:
4983
+ value = null;
4984
+ }
4985
+ if (value) return value;
4986
+ }
4987
+ return "";
4988
+ }
4557
4989
 
4558
4990
  // src/daemon/locate.ts
4559
4991
  import { execFile as execFile4, execFileSync as execFileSync9 } from "child_process";
4560
4992
  import { accessSync, closeSync as closeSync4, constants, existsSync as existsSync17, lstatSync, openSync as openSync4, readFileSync as readFileSync22, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
4561
- import { homedir as homedir6 } from "os";
4993
+ import { homedir as homedir7 } from "os";
4562
4994
  import { delimiter as delimiter2, dirname as dirname11, join as join20, win32 as win325 } from "path";
4563
4995
 
4564
4996
  // src/core/agentEnv.ts
@@ -4856,7 +5288,7 @@ function qoderWorker(d, worker, appBin) {
4856
5288
  return { file: d.nodePath, prefixArgs: [worker] };
4857
5289
  }
4858
5290
  var defaultLocateDeps = () => ({
4859
- home: homedir6(),
5291
+ home: homedir7(),
4860
5292
  path: process.env.PATH ?? "",
4861
5293
  nodePath: process.execPath,
4862
5294
  isExecutable: (p) => {
@@ -5540,7 +5972,7 @@ function markElectronBroken(v = true) {
5540
5972
  electronBroken = v;
5541
5973
  }
5542
5974
  var requireHere2 = createRequire3(import.meta.url);
5543
- function loadNodeSqlite() {
5975
+ function loadNodeSqlite2() {
5544
5976
  const orig = process.emitWarning;
5545
5977
  process.emitWarning = function(...args) {
5546
5978
  if (String(args[0] ?? "").includes("SQLite")) return;
@@ -5626,7 +6058,7 @@ function savePendingFile(jobs) {
5626
6058
  var defaultWbAppDbDeps = () => ({
5627
6059
  dbPath: () => workbuddyDbPath(),
5628
6060
  configDir: () => dirname12(workbuddySettingsPath()),
5629
- loadSqlite: loadNodeSqlite,
6061
+ loadSqlite: loadNodeSqlite2,
5630
6062
  electron: () => defaultWbElectron(),
5631
6063
  runElectron: (bin, script, input) => new Promise((resolve7, reject) => {
5632
6064
  const env = { ...process.env, ELECTRON_RUN_AS_NODE: "1" };
@@ -5909,6 +6341,52 @@ var WsCdpClient = class {
5909
6341
 
5910
6342
  // src/daemon/workbuddyApp/keeper.ts
5911
6343
  import { posix as posix2, win32 as win326 } from "path";
6344
+
6345
+ // src/daemon/userIdle.ts
6346
+ function parseHidIdleMs(out) {
6347
+ const m = /"HIDIdleTime"\s*=\s*(\d+)/.exec(out);
6348
+ return m ? Math.floor(Number(m[1]) / 1e6) : null;
6349
+ }
6350
+ var WIN_PRESENCE_PS = String.raw`Add-Type @"
6351
+ using System; using System.Runtime.InteropServices;
6352
+ public class DbUserIdle {
6353
+ [StructLayout(LayoutKind.Sequential)] public struct LII { public uint cbSize; public uint dwTime; }
6354
+ [DllImport("user32.dll")] public static extern bool GetLastInputInfo(ref LII p);
6355
+ [DllImport("kernel32.dll")] public static extern uint GetTickCount();
6356
+ [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
6357
+ [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
6358
+ }
6359
+ "@
6360
+ $l = New-Object DbUserIdle+LII; $l.cbSize = 8
6361
+ if (-not [DbUserIdle]::GetLastInputInfo([ref]$l)) { exit 1 }
6362
+ $idle = (([uint64][DbUserIdle]::GetTickCount()) - [uint64]$l.dwTime + 4294967296) % 4294967296
6363
+ $fp = [uint32]0; [void][DbUserIdle]::GetWindowThreadProcessId([DbUserIdle]::GetForegroundWindow(), [ref]$fp)
6364
+ $n = ''; try { $n = (Get-Process -Id $fp -ErrorAction Stop).ProcessName } catch {}
6365
+ Write-Output ("{0}|{1}" -f $idle, $n)`;
6366
+ function parseWinPresence(code, stdout2) {
6367
+ const m = /^(\d+)\|(.*)$/m.exec(stdout2.trim());
6368
+ if (code !== 0 || !m) return null;
6369
+ return { idleMs: Number(m[1]), front: m[2].trim() };
6370
+ }
6371
+ async function macIdleMs(exec) {
6372
+ const r = await exec("ioreg", ["-c", "IOHIDSystem", "-d", "4"]);
6373
+ return r.code === 0 ? parseHidIdleMs(r.stdout) : null;
6374
+ }
6375
+ async function winPresence(exec) {
6376
+ const r = await exec("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", WIN_PRESENCE_PS], 15e3);
6377
+ return parseWinPresence(r.code, r.stdout);
6378
+ }
6379
+ async function userIdleMs(exec, platform = process.platform) {
6380
+ try {
6381
+ if (platform === "darwin") return await macIdleMs(exec);
6382
+ if (platform === "win32") return (await winPresence(exec))?.idleMs ?? null;
6383
+ } catch {
6384
+ return null;
6385
+ }
6386
+ return null;
6387
+ }
6388
+
6389
+ // src/daemon/workbuddyApp/keeper.ts
5912
6390
  var DEFAULT_CDP_PORT = 39217;
5913
6391
  var ENV_NAME = "WORKBUDDY_REMOTE_DEBUGGING_PORT";
5914
6392
  var RESTART_GAP_MS = 30 * 60 * 1e3;
@@ -5989,26 +6467,6 @@ var filePersist = {
5989
6467
  }
5990
6468
  }
5991
6469
  };
5992
- function parseHidIdleMs(out) {
5993
- const m = /"HIDIdleTime"\s*=\s*(\d+)/.exec(out);
5994
- return m ? Math.floor(Number(m[1]) / 1e6) : null;
5995
- }
5996
- var WIN_PRESENCE_PS = String.raw`Add-Type @"
5997
- using System; using System.Runtime.InteropServices;
5998
- public class DbUserIdle {
5999
- [StructLayout(LayoutKind.Sequential)] public struct LII { public uint cbSize; public uint dwTime; }
6000
- [DllImport("user32.dll")] public static extern bool GetLastInputInfo(ref LII p);
6001
- [DllImport("kernel32.dll")] public static extern uint GetTickCount();
6002
- [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
6003
- [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
6004
- }
6005
- "@
6006
- $l = New-Object DbUserIdle+LII; $l.cbSize = 8
6007
- if (-not [DbUserIdle]::GetLastInputInfo([ref]$l)) { exit 1 }
6008
- $idle = (([uint64][DbUserIdle]::GetTickCount()) - [uint64]$l.dwTime + 4294967296) % 4294967296
6009
- $fp = [uint32]0; [void][DbUserIdle]::GetWindowThreadProcessId([DbUserIdle]::GetForegroundWindow(), [ref]$fp)
6010
- $n = ''; try { $n = (Get-Process -Id $fp -ErrorAction Stop).ProcessName } catch {}
6011
- Write-Output ("{0}|{1}" -f $idle, $n)`;
6012
6470
  var WAKE_LAUNCH_GAP_MS = 60 * 1e3;
6013
6471
  var APP_PATH_TTL_MS = 5 * 60 * 1e3;
6014
6472
  function defaultWorkbuddyAppPath() {
@@ -6263,7 +6721,7 @@ var CdpKeeper = class {
6263
6721
  if (this.#d.userAway) return await this.#d.userAway();
6264
6722
  const exec = this.#exec();
6265
6723
  if (this.#platform() === "darwin") {
6266
- const idle = parseHidIdleMs((await exec("ioreg", ["-c", "IOHIDSystem", "-d", "4"])).stdout);
6724
+ const idle = await macIdleMs(exec);
6267
6725
  if (idle === null || idle < AWAY_IDLE_MS) return false;
6268
6726
  const asn = (await exec("lsappinfo", ["front"])).stdout.trim();
6269
6727
  if (!asn) return false;
@@ -6271,10 +6729,9 @@ var CdpKeeper = class {
6271
6729
  return !!name.trim() && !/WorkBuddy/i.test(name);
6272
6730
  }
6273
6731
  if (this.#platform() === "win32") {
6274
- const r = await exec("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", WIN_PRESENCE_PS], 15e3);
6275
- const m = /^(\d+)\|(.*)$/m.exec(r.stdout.trim());
6276
- if (r.code !== 0 || !m) return false;
6277
- return Number(m[1]) >= AWAY_IDLE_MS && !/^WorkBuddy$/i.test(m[2].trim());
6732
+ const p = await winPresence(exec);
6733
+ if (!p) return false;
6734
+ return p.idleMs >= AWAY_IDLE_MS && !/^WorkBuddy$/i.test(p.front);
6278
6735
  }
6279
6736
  } catch {
6280
6737
  return false;
@@ -8535,7 +8992,7 @@ function runHook(node, input, agentPid, tool = "dumate") {
8535
8992
 
8536
8993
  // src/daemon/qoderApp/keeper.ts
8537
8994
  import { existsSync as existsSync20, mkdirSync as mkdirSync22, readdirSync as readdirSync12, readFileSync as readFileSync25, renameSync as renameSync16, writeFileSync as writeFileSync22 } from "fs";
8538
- import { homedir as homedir7 } from "os";
8995
+ import { homedir as homedir8 } from "os";
8539
8996
  import { join as join24, win32 as win327 } from "path";
8540
8997
  var WAKE_LAUNCH_GAP_MS2 = 60 * 1e3;
8541
8998
  var RESTART_GAP_MS2 = 30 * 60 * 1e3;
@@ -8594,10 +9051,6 @@ function hasMainProc(psOut, brand) {
8594
9051
  }
8595
9052
  return false;
8596
9053
  }
8597
- function parseHidIdleMs2(out) {
8598
- const m = /"HIDIdleTime"\s*=\s*(\d+)/.exec(out);
8599
- return m ? Math.floor(Number(m[1]) / 1e6) : null;
8600
- }
8601
9054
  function parseLsappBundle(out) {
8602
9055
  const m = /"CFBundleIdentifier"\s*=\s*"([^"]+)"/.exec(out);
8603
9056
  return m ? m[1] : null;
@@ -8836,7 +9289,7 @@ var QoderCdpKeeper = class {
8836
9289
  }
8837
9290
  if (this.#platform() !== "darwin") return null;
8838
9291
  const name = this.#d.brand.macApp.split("/").pop() ?? "";
8839
- for (const p of [this.#d.brand.macApp, join24(homedir7(), "Applications", name)]) if (existsSync20(p)) return p;
9292
+ for (const p of [this.#d.brand.macApp, join24(homedir8(), "Applications", name)]) if (existsSync20(p)) return p;
8840
9293
  return null;
8841
9294
  }
8842
9295
  /** 一轮:健康检查 → 活着核对属主。🔴 平时不重启 App;只有端口无响应满 HUNG_RESTART_MS 时按节流与在场规则重启 */
@@ -8878,10 +9331,9 @@ var QoderCdpKeeper = class {
8878
9331
  async userAway() {
8879
9332
  if (this.#isWin() && !this.#d.frontBundle && !this.#d.idleMs) {
8880
9333
  try {
8881
- const r = await this.#exec()("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", WIN_PRESENCE_PS], 15e3);
8882
- const m = /^(\d+)\|(.*)$/m.exec(r.stdout.trim());
8883
- if (r.code !== 0 || !m) return false;
8884
- return Number(m[1]) >= USER_IDLE_MS && !!m[2].trim() && !this.#d.brand.win.fgName.test(m[2].trim());
9334
+ const p = await winPresence(this.#exec());
9335
+ if (!p) return false;
9336
+ return p.idleMs >= USER_IDLE_MS && !!p.front && !this.#d.brand.win.fgName.test(p.front);
8885
9337
  } catch {
8886
9338
  return false;
8887
9339
  }
@@ -8904,8 +9356,7 @@ var QoderCdpKeeper = class {
8904
9356
  return parseLsappBundle((await exec("lsappinfo", ["info", "-only", "bundleid", asn])).stdout);
8905
9357
  }
8906
9358
  async #defaultIdle() {
8907
- const r = await this.#exec()("ioreg", ["-c", "IOHIDSystem", "-d", "4"]);
8908
- return r.code === 0 ? parseHidIdleMs2(r.stdout) : null;
9359
+ return macIdleMs(this.#exec());
8909
9360
  }
8910
9361
  /** 主进程(macOS;只认唯一一个;没有 / 认不出 / 多个 → null) */
8911
9362
  async #brandMain() {
@@ -10272,12 +10723,12 @@ function migrateLegacyQoder(out, detect = () => detectTools()) {
10272
10723
  // src/daemon/loginGuide.ts
10273
10724
  import { execFile as execFile9, execFileSync as execFileSync13 } from "child_process";
10274
10725
  import { chmodSync as chmodSync9, existsSync as existsSync27, mkdirSync as mkdirSync26, readFileSync as readFileSync30, writeFileSync as writeFileSync26 } from "fs";
10275
- import { homedir as homedir9 } from "os";
10726
+ import { homedir as homedir10 } from "os";
10276
10727
  import { join as join35 } from "path";
10277
10728
 
10278
10729
  // src/daemon/executor.ts
10279
10730
  import { execFile as execFile7, execFileSync as execFileSync11 } from "child_process";
10280
- import { homedir as homedir8 } from "os";
10731
+ import { homedir as homedir9 } from "os";
10281
10732
  import { join as join33 } from "path";
10282
10733
 
10283
10734
  // src/core/codebuddyDirs.ts
@@ -10371,7 +10822,7 @@ var TOOL_BINARY = {
10371
10822
  opencode: "opencode",
10372
10823
  deepseek_harness: "dsh"
10373
10824
  };
10374
- function buildSpawnPlan(cmd, cwd, gate, home = homedir8(), configDir, platform = process.platform) {
10825
+ function buildSpawnPlan(cmd, cwd, gate, home = homedir9(), configDir, platform = process.platform) {
10375
10826
  const file3 = TOOL_BINARY[cmd.tool];
10376
10827
  if (!file3) return null;
10377
10828
  switch (cmd.tool) {
@@ -10951,7 +11402,7 @@ var defaultCheckDeps = () => ({
10951
11402
  run: defaultProbeRun,
10952
11403
  env: process.env,
10953
11404
  childPath: () => childPath(),
10954
- home: homedir9(),
11405
+ home: homedir10(),
10955
11406
  codexConfig: () => {
10956
11407
  try {
10957
11408
  return readFileSync30(join35(codexHome(), "config.toml"), "utf8");
@@ -10963,7 +11414,7 @@ var defaultCheckDeps = () => ({
10963
11414
  workbuddyKind: () => locateWorkbuddy()?.kind ?? null,
10964
11415
  // 与设备任务唤起 App 同一个 locate(locateWorkbuddyAppExe:注册表卸载项 / App Paths / 开始菜单快捷方式…);App 自带 CLI 找得到也算
10965
11416
  workbuddyAppInstalled: () => locateWorkbuddyAppExe() !== null || locateWorkbuddyApp() !== null,
10966
- workbuddyAuthFile: () => workbuddyAuthFile(homedir9(), process.platform, process.env.LOCALAPPDATA ?? ""),
11417
+ workbuddyAuthFile: () => workbuddyAuthFile(homedir10(), process.platform, process.env.LOCALAPPDATA ?? ""),
10967
11418
  exists: (p) => existsSync27(p)
10968
11419
  });
10969
11420
  function daemonCheckDeps(base = defaultLocateDeps(), exeHintsAsync = winWorkbuddyLocatorHints) {
@@ -10986,7 +11437,7 @@ var SAFE_ARGS = /* @__PURE__ */ new Set(["auth", "login"]);
10986
11437
  var defaultCommandDeps = () => ({
10987
11438
  locate: (tool) => locate(tool),
10988
11439
  locateWorkbuddyTui: () => locateWorkbuddyTui(),
10989
- home: homedir9()
11440
+ home: homedir10()
10990
11441
  });
10991
11442
  var WORKBUDDY_LOGIN_HINT = "\uFF08\u5982\u679C\u5148\u5F39\u51FA\u4FE1\u4EFB\u6846\uFF0C\u9009 1 \u56DE\u8F66\uFF1B\u51FA\u73B0 Select login method \u65F6\u9009 Log in via Chinese Site \u56DE\u8F66\uFF0C\u5230\u6D4F\u89C8\u5668\u5B8C\u6210\u767B\u5F55\uFF1B\u82E5\u53EA\u770B\u5230\u8F93\u5165\u6846\uFF0C\u8F93\u5165 /login \u56DE\u8F66\uFF09";
10992
11443
  var WORKBUDDY_NEED_STANDALONE = "\u53EA\u88C5\u4E86 WorkBuddy App\uFF0C\u5B83\u81EA\u5E26\u7684\u547D\u4EE4\u884C\u7528\u4E0D\u4E86 App \u7684\u767B\u5F55\uFF1B\u8BBE\u5907\u4E0A\u65B0\u5EFA / \u56DE\u590D\u4EFB\u52A1\u9700\u8981\u72EC\u7ACB\u7248\u547D\u4EE4\u884C\uFF08\u8FD0\u884C deskbuddy workbuddy-setup \u5B89\u88C5\uFF09";
@@ -11112,7 +11563,7 @@ var defaultLoginOpenDeps = () => ({
11112
11563
  });
11113
11564
  async function installCodebuddyHooks(log) {
11114
11565
  try {
11115
- const { installTools: installTools2 } = await import("./enable-AF2A67ZD.js");
11566
+ const { installTools: installTools2 } = await import("./enable-OICEMNUF.js");
11116
11567
  installTools2(["workbuddy"], () => {
11117
11568
  }, null, { auto: true });
11118
11569
  } catch (e) {
@@ -11626,7 +12077,7 @@ function defaultOfferDeps(log) {
11626
12077
  }),
11627
12078
  openLogin: () => defaultOpenLogin(platform),
11628
12079
  installHooks: async () => {
11629
- const { installTools: installTools2 } = await import("./enable-AF2A67ZD.js");
12080
+ const { installTools: installTools2 } = await import("./enable-OICEMNUF.js");
11630
12081
  installTools2(["workbuddy"], (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`), null, { auto: true });
11631
12082
  },
11632
12083
  log: (l) => log(`[WorkBuddy \u72EC\u7ACB\u7248] ${l}`),
@@ -11962,6 +12413,7 @@ export {
11962
12413
  detectTools,
11963
12414
  loadRegistry,
11964
12415
  refreshRegistry,
12416
+ titleSources,
11965
12417
  waitModeFor,
11966
12418
  shQuote,
11967
12419
  winCmdPath,
@@ -12002,8 +12454,10 @@ export {
12002
12454
  isAppSession,
12003
12455
  stateDir,
12004
12456
  readState,
12457
+ renameRetrying,
12005
12458
  writeState,
12006
12459
  retireState,
12460
+ removeState,
12007
12461
  STALE_NO_PID_LOG,
12008
12462
  readAliveStates,
12009
12463
  writeNotice,
@@ -12045,11 +12499,13 @@ export {
12045
12499
  isReportableDir,
12046
12500
  cleanTitle,
12047
12501
  qoderDbPaths,
12502
+ resolveTitle,
12048
12503
  findWbTranscript,
12049
12504
  wbTranscriptInfo,
12050
12505
  registerWorkbuddySession,
12051
12506
  flushPendingWorkbuddy,
12052
12507
  connectCdp,
12508
+ userIdleMs,
12053
12509
  DEFAULT_CDP_PORT,
12054
12510
  defaultExec,
12055
12511
  APP_PORT_HUNG_REASON,