deskbuddy 0.2.0-beta.26 → 0.2.0-beta.27
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.
- package/dist/cli.js +1377 -531
- package/dist/gate.cjs +37 -17
- package/hook/gate.cjs +37 -17
- package/hook/hook-debug.log +15 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -716,7 +716,7 @@ var AutoEnabler = class {
|
|
|
716
716
|
// src/daemon/index.ts
|
|
717
717
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
718
718
|
import { existsSync as existsSync13, statSync as statSync13 } from "fs";
|
|
719
|
-
import { basename as basename10, dirname as dirname10, join as
|
|
719
|
+
import { basename as basename10, dirname as dirname10, join as join28 } from "path";
|
|
720
720
|
import WebSocket3 from "ws";
|
|
721
721
|
|
|
722
722
|
// src/core/state.ts
|
|
@@ -1677,7 +1677,7 @@ function mapStatus(s2, decision) {
|
|
|
1677
1677
|
import { randomUUID } from "crypto";
|
|
1678
1678
|
import { lstatSync, readlinkSync, realpathSync as realpathSync2 } from "fs";
|
|
1679
1679
|
import { homedir as homedir2 } from "os";
|
|
1680
|
-
import { basename as
|
|
1680
|
+
import { basename as basename5, dirname as dirname2, join as join10, posix as posix3, resolve as resolve2, win32 as win323 } from "path";
|
|
1681
1681
|
|
|
1682
1682
|
// src/daemon/gate/classify.ts
|
|
1683
1683
|
import { isAbsolute, relative, resolve, sep } from "path";
|
|
@@ -2114,8 +2114,9 @@ var CMD_MATCH_MIN = 6;
|
|
|
2114
2114
|
var CMD_MATCH_LEN = 80;
|
|
2115
2115
|
|
|
2116
2116
|
// src/daemon/gate/options.ts
|
|
2117
|
-
var
|
|
2118
|
-
var
|
|
2117
|
+
var OPTIONS_MAX = 16;
|
|
2118
|
+
var OPTION_LABEL_MAX = 2e3;
|
|
2119
|
+
var OPTION_DESC_MAX = 2e3;
|
|
2119
2120
|
var ID_RE = /^[a-z0-9_]{1,16}$/;
|
|
2120
2121
|
var KINDS = /* @__PURE__ */ new Set(["allow", "allow_session", "allow_always", "deny"]);
|
|
2121
2122
|
function cleanOptionText(s2) {
|
|
@@ -2124,9 +2125,8 @@ function cleanOptionText(s2) {
|
|
|
2124
2125
|
function checkOptions(set, limits = {}) {
|
|
2125
2126
|
if (!set || !Array.isArray(set.options)) return null;
|
|
2126
2127
|
const opts = set.options;
|
|
2127
|
-
const unbounded = limits.unbounded === true;
|
|
2128
2128
|
const anyKinds = limits.anyKinds === true;
|
|
2129
|
-
if (opts.length < (anyKinds ? 1 : 2) ||
|
|
2129
|
+
if (opts.length < (anyKinds ? 1 : 2) || opts.length > OPTIONS_MAX) return null;
|
|
2130
2130
|
const ids = /* @__PURE__ */ new Set();
|
|
2131
2131
|
const out = [];
|
|
2132
2132
|
for (const o of opts) {
|
|
@@ -2135,8 +2135,9 @@ function checkOptions(set, limits = {}) {
|
|
|
2135
2135
|
if (!KINDS.has(o.kind)) return null;
|
|
2136
2136
|
if (o.input === true && o.kind !== "deny") return null;
|
|
2137
2137
|
const label = typeof o.label === "string" ? cleanOptionText(o.label) : "";
|
|
2138
|
-
if (!label ||
|
|
2139
|
-
const desc = typeof o.description === "string" ?
|
|
2138
|
+
if (!label || label.length > OPTION_LABEL_MAX) return null;
|
|
2139
|
+
const desc = typeof o.description === "string" ? cleanOptionText(o.description) : "";
|
|
2140
|
+
if (desc.length > OPTION_DESC_MAX) return null;
|
|
2140
2141
|
const a = set.actions?.[o.id];
|
|
2141
2142
|
if (!a || a.decision !== (o.kind === "deny" ? "deny" : "allow")) return null;
|
|
2142
2143
|
if (a.defer === true && o.kind !== "deny") return null;
|
|
@@ -2417,13 +2418,10 @@ function qoderCnTuiKnown(toolName, toolInput, suggestions) {
|
|
|
2417
2418
|
if (!s2 || s2.type !== "setMode" || s2.mode !== "acceptEdits" || s2.destination !== "session") return null;
|
|
2418
2419
|
const set = qoderSet(...remember("session", QODER_TEXT.session, "allow_session", "\u89C4\u5219\uFF1A\u672C\u4F1A\u8BDD\u5207\u6362\u5230 acceptEdits\uFF08\u81EA\u52A8\u63A5\u53D7\u6587\u4EF6\u7F16\u8F91\uFF09"));
|
|
2419
2420
|
if (!set) return null;
|
|
2420
|
-
return checkOptions(
|
|
2421
|
-
{
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
},
|
|
2425
|
-
{ unbounded: true }
|
|
2426
|
-
);
|
|
2421
|
+
return checkOptions({
|
|
2422
|
+
options: [...set.options, { id: "editor", label: QODER_TEXT.editor, description: QODER_TEXT.editorDesc, kind: "deny" }],
|
|
2423
|
+
actions: { ...set.actions, editor: { decision: "deny", defer: true } }
|
|
2424
|
+
});
|
|
2427
2425
|
}
|
|
2428
2426
|
if (toolName === "WebFetch") {
|
|
2429
2427
|
const rc = s2 ? singleRule(s2, "WebFetch", "localSettings") : null;
|
|
@@ -2443,6 +2441,45 @@ function qoderCnTuiKnown(toolName, toolInput, suggestions) {
|
|
|
2443
2441
|
}
|
|
2444
2442
|
return null;
|
|
2445
2443
|
}
|
|
2444
|
+
var QODER_SCREEN_UNKNOWN_DESC = "\u8981\u5728\u7535\u8111\u4E0A\u64CD\u4F5C\uFF0C\u8BBE\u5907\u4E0A\u9009\u8FD9\u4E00\u9879\u540E\u8BF7\u5230\u7535\u8111\u4E0A\u7EE7\u7EED";
|
|
2445
|
+
var SCREEN_DENY_RE = /^(?:No\b|Reject\b|Deny\b|Don'?t\b|Do not\b|Cancel\b|拒绝|不)/i;
|
|
2446
|
+
var SCREEN_INPUT_RE = /tell .+ what to do|type something|拒绝并说明/i;
|
|
2447
|
+
var SCREEN_ALLOW_RE = /^(?:Allow\b|Always\b|Yes\b|允许|始终允许|总是允许)/i;
|
|
2448
|
+
function qoderCnScreenOptions(labels, table) {
|
|
2449
|
+
if (!Array.isArray(labels) || labels.length < 2) return null;
|
|
2450
|
+
const main2 = [];
|
|
2451
|
+
const tail3 = [];
|
|
2452
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2453
|
+
const take = (id, n) => ids.has(id) ? `k${n}` : id;
|
|
2454
|
+
for (let i = 0; i < labels.length; i++) {
|
|
2455
|
+
const n = i + 1;
|
|
2456
|
+
const label = cleanOptionText(String(labels[i] ?? ""));
|
|
2457
|
+
if (!label) return null;
|
|
2458
|
+
let pair;
|
|
2459
|
+
const known = table?.options.find((o) => o.label === label);
|
|
2460
|
+
const knownAct = known ? table?.actions[known.id] : void 0;
|
|
2461
|
+
if (label === QODER_TEXT.once) pair = [{ id: take("allow", n), label, kind: "allow" }, { decision: "allow" }];
|
|
2462
|
+
else if (label === QODER_TEXT.no) pair = [{ id: take("no", n), label, kind: "deny" }, { decision: "deny" }];
|
|
2463
|
+
else if (label === QODER_TEXT.rejectInput)
|
|
2464
|
+
pair = [{ id: take("deny", n), label, description: QODER_TEXT.rejectInputDesc, kind: "deny", input: true }, { decision: "deny" }];
|
|
2465
|
+
else if (label === QODER_TEXT.editor)
|
|
2466
|
+
pair = [{ id: take("editor", n), label, description: QODER_TEXT.editorDesc, kind: "deny" }, { decision: "deny", defer: true }];
|
|
2467
|
+
else if (known && knownAct && !knownAct.defer && known.kind !== "allow")
|
|
2468
|
+
pair = [{ ...known, id: take(known.id, n) }, knownAct];
|
|
2469
|
+
else if (SCREEN_DENY_RE.test(label))
|
|
2470
|
+
pair = [{ id: `k${n}`, label, kind: "deny", ...SCREEN_INPUT_RE.test(label) ? { input: true } : {} }, { decision: "deny" }];
|
|
2471
|
+
else if (SCREEN_ALLOW_RE.test(label))
|
|
2472
|
+
pair = [{ id: `k${n}`, label, kind: /session|会话/i.test(label) ? "allow_session" : "allow_always" }, { decision: "allow", screenKey: n }];
|
|
2473
|
+
else pair = [{ id: `k${n}`, label, description: QODER_SCREEN_UNKNOWN_DESC, kind: "deny" }, { decision: "deny", defer: true }];
|
|
2474
|
+
ids.add(pair[0].id);
|
|
2475
|
+
(pair[1].defer ? tail3 : main2).push(pair);
|
|
2476
|
+
}
|
|
2477
|
+
const all = [...main2, ...tail3];
|
|
2478
|
+
const options = all.map(([o]) => o);
|
|
2479
|
+
const actions = {};
|
|
2480
|
+
for (const [o, a] of all) actions[o.id] = a;
|
|
2481
|
+
return checkOptions({ options, actions });
|
|
2482
|
+
}
|
|
2446
2483
|
var QODER_GUI_TEXT = {
|
|
2447
2484
|
zh: {
|
|
2448
2485
|
deny: "\u62D2\u7EDD",
|
|
@@ -2518,7 +2555,25 @@ function qoderCnGuiOptions(suggestions, lang) {
|
|
|
2518
2555
|
options.push({ id, label: v.label, ...v.desc ? { description: v.desc } : {}, kind: "allow_session" });
|
|
2519
2556
|
actions[id] = { decision: "allow", updatedPermissions: [s2] };
|
|
2520
2557
|
}
|
|
2521
|
-
return checkOptions({ options, actions }
|
|
2558
|
+
return checkOptions({ options, actions });
|
|
2559
|
+
}
|
|
2560
|
+
var QWENWORK_TEXT = {
|
|
2561
|
+
zh: { deny: "\u62D2\u7EDD", allow: "\u5141\u8BB8" },
|
|
2562
|
+
"zh-TW": { deny: "\u62D2\u7D55", allow: "\u5141\u8A31" },
|
|
2563
|
+
en: { deny: "Deny", allow: "Allow" }
|
|
2564
|
+
};
|
|
2565
|
+
var QWENWORK_CONFIRM_TOOLS = /(^|__)qwenwork_pages_publish$/;
|
|
2566
|
+
function qwenworkOptions(toolName, lang) {
|
|
2567
|
+
if (QWENWORK_CONFIRM_TOOLS.test(toolName)) return codebuddyNoticeOptions();
|
|
2568
|
+
if (lang !== "zh" && lang !== "en" && lang !== "zh-TW") return null;
|
|
2569
|
+
const t = QWENWORK_TEXT[lang];
|
|
2570
|
+
return checkOptions({
|
|
2571
|
+
options: [
|
|
2572
|
+
{ id: "deny", label: t.deny, kind: "deny" },
|
|
2573
|
+
{ id: "allow", label: t.allow, kind: "allow" }
|
|
2574
|
+
],
|
|
2575
|
+
actions: { deny: { decision: "deny" }, allow: { decision: "allow" } }
|
|
2576
|
+
});
|
|
2522
2577
|
}
|
|
2523
2578
|
function firstRememberOption(set) {
|
|
2524
2579
|
return set?.options.find((o) => o.kind === "allow_session" || o.kind === "allow_always");
|
|
@@ -2598,10 +2653,537 @@ function codebuddyOptions(toolName, toolInput, product, ctx = {}) {
|
|
|
2598
2653
|
function codebuddyNoticeOptions() {
|
|
2599
2654
|
return checkOptions(
|
|
2600
2655
|
{ options: [{ id: "o0", label: CODEBUDDY_TEXT.noticeButton, kind: "deny" }], actions: { o0: { decision: "deny", defer: true } } },
|
|
2601
|
-
{ anyKinds: true
|
|
2656
|
+
{ anyKinds: true }
|
|
2602
2657
|
);
|
|
2603
2658
|
}
|
|
2604
2659
|
|
|
2660
|
+
// src/wrap/answerKeys.ts
|
|
2661
|
+
var KEY_DOWN = "\x1B[B";
|
|
2662
|
+
var KEY_UP = "\x1B[A";
|
|
2663
|
+
var KEY_ENTER = "\r";
|
|
2664
|
+
var ARROW_GAP_MS = 150;
|
|
2665
|
+
var CONFIRM_MS = 4e3;
|
|
2666
|
+
var POLL_MS = 150;
|
|
2667
|
+
var FINGERPRINT_CHARS = 40;
|
|
2668
|
+
var FINGERPRINT_TAIL = 30;
|
|
2669
|
+
var FP_SEP = "\u2026";
|
|
2670
|
+
var CURSOR = "[\u276F>\u203A]";
|
|
2671
|
+
function joined(lines3) {
|
|
2672
|
+
return lines3.join("\n");
|
|
2673
|
+
}
|
|
2674
|
+
function squash(s2) {
|
|
2675
|
+
return s2.replace(/[\s\u2500-\u257F]+/g, "");
|
|
2676
|
+
}
|
|
2677
|
+
function basename4(p) {
|
|
2678
|
+
const parts = p.split(/[\\/]+/).filter(Boolean);
|
|
2679
|
+
return parts[parts.length - 1] ?? "";
|
|
2680
|
+
}
|
|
2681
|
+
function displayFingerprint(toolName, input) {
|
|
2682
|
+
const a = input && typeof input === "object" ? input : {};
|
|
2683
|
+
const s2 = (v) => typeof v === "string" ? v : "";
|
|
2684
|
+
const headTail = (v) => {
|
|
2685
|
+
const q = squash(v);
|
|
2686
|
+
return q.length > FINGERPRINT_CHARS + FINGERPRINT_TAIL ? q.slice(0, FINGERPRINT_CHARS) + FP_SEP + q.slice(-FINGERPRINT_TAIL) : q;
|
|
2687
|
+
};
|
|
2688
|
+
if (toolName === "Bash") return headTail(s2(a.command));
|
|
2689
|
+
if (toolName === "Write") {
|
|
2690
|
+
const name = squash(basename4(s2(a.file_path)));
|
|
2691
|
+
const body = squash(s2(a.content)).slice(0, 20);
|
|
2692
|
+
return name ? body ? name + FP_SEP + body : name : "";
|
|
2693
|
+
}
|
|
2694
|
+
if (["Edit", "MultiEdit", "Read", "NotebookEdit"].includes(toolName)) return squash(basename4(s2(a.file_path) || s2(a.notebook_path)));
|
|
2695
|
+
return headTail(Object.values(a).find((v) => typeof v === "string" && v.trim() !== "") ?? "").slice(0, 200);
|
|
2696
|
+
}
|
|
2697
|
+
var SETTLE_MS = 800;
|
|
2698
|
+
async function settled(io, ok) {
|
|
2699
|
+
if (!ok(io.screen())) return false;
|
|
2700
|
+
await io.sleep(SETTLE_MS);
|
|
2701
|
+
return ok(io.screen());
|
|
2702
|
+
}
|
|
2703
|
+
async function waitUntil(io, ok, ms = CONFIRM_MS) {
|
|
2704
|
+
const loops = Math.max(1, Math.ceil(ms / POLL_MS));
|
|
2705
|
+
for (let i = 0; i < loops; i++) {
|
|
2706
|
+
if (ok(io.screen())) return true;
|
|
2707
|
+
await io.sleep(POLL_MS);
|
|
2708
|
+
}
|
|
2709
|
+
return ok(io.screen());
|
|
2710
|
+
}
|
|
2711
|
+
function permissionRegion(lines3, toolName) {
|
|
2712
|
+
let q = -1;
|
|
2713
|
+
for (let i = lines3.length - 1; i >= 0; i--) {
|
|
2714
|
+
if (/^\s*Do you want to .+\?\s*$/.test(lines3[i])) {
|
|
2715
|
+
q = i;
|
|
2716
|
+
break;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
if (q < 0 || !toolName) return null;
|
|
2720
|
+
const head = new RegExp(`^\\s*\u25CF\\s*${toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\(`);
|
|
2721
|
+
let h = -1;
|
|
2722
|
+
for (let i = q - 1; i >= 0; i--) {
|
|
2723
|
+
if (head.test(lines3[i])) {
|
|
2724
|
+
h = i;
|
|
2725
|
+
break;
|
|
2726
|
+
}
|
|
2727
|
+
if (/^\s*●/.test(lines3[i])) return null;
|
|
2728
|
+
}
|
|
2729
|
+
if (h < 0) return null;
|
|
2730
|
+
const after = joined(lines3.slice(q + 1, q + 6));
|
|
2731
|
+
if (!new RegExp(`^\\s*(?:${CURSOR}\\s*)?1\\.\\s+Yes\\s*$`, "m").test(after)) return null;
|
|
2732
|
+
if (!/^\s*(?:[❯>›]\s*)?3\.\s+No\b/m.test(after)) return null;
|
|
2733
|
+
return lines3.slice(h, q + 1);
|
|
2734
|
+
}
|
|
2735
|
+
function isPermissionDialog(lines3, toolName, fingerprint2) {
|
|
2736
|
+
if (!fingerprint2) return false;
|
|
2737
|
+
const region = permissionRegion(lines3, toolName);
|
|
2738
|
+
if (!region) return false;
|
|
2739
|
+
const text = squash(joined(region));
|
|
2740
|
+
return fingerprint2.split(FP_SEP).every((part) => !!part && text.includes(squash(part)));
|
|
2741
|
+
}
|
|
2742
|
+
async function answerPermission(io, toolName, fingerprint2, decision) {
|
|
2743
|
+
const ok = (l) => isPermissionDialog(l, toolName, fingerprint2);
|
|
2744
|
+
if (!await settled(io, ok)) return "not_waiting";
|
|
2745
|
+
io.write(decision === "allow" ? "1" : "3");
|
|
2746
|
+
return await waitUntil(io, (l) => !ok(l)) ? "done" : "partial";
|
|
2747
|
+
}
|
|
2748
|
+
var TAB_ROW = /←.*[☐☒].*Submit.*→/;
|
|
2749
|
+
function isQuestionDialog(lines3) {
|
|
2750
|
+
return lines3.some((l) => TAB_ROW.test(l));
|
|
2751
|
+
}
|
|
2752
|
+
function onQuestion(lines3, q) {
|
|
2753
|
+
const start = questionStart(lines3);
|
|
2754
|
+
if (start < 0) return false;
|
|
2755
|
+
const t = squash(q.question);
|
|
2756
|
+
return !!t && squash(joined(lines3.slice(start))).includes(t);
|
|
2757
|
+
}
|
|
2758
|
+
function isReviewPage(lines3) {
|
|
2759
|
+
const t = joined(lines3);
|
|
2760
|
+
return /Review your answers/.test(t) && /Submit answers/.test(t);
|
|
2761
|
+
}
|
|
2762
|
+
function questionStart(lines3) {
|
|
2763
|
+
for (let i = lines3.length - 1; i >= 0; i--) if (TAB_ROW.test(lines3[i])) return i + 1;
|
|
2764
|
+
return -1;
|
|
2765
|
+
}
|
|
2766
|
+
function optionLine(lines3, n, label) {
|
|
2767
|
+
const lab = squash(label).slice(0, 6);
|
|
2768
|
+
const start = questionStart(lines3);
|
|
2769
|
+
if (start < 0) return -1;
|
|
2770
|
+
for (let i = start; i < lines3.length; i++) {
|
|
2771
|
+
const m = lines3[i].match(/^\s*(?:[❯>›]\s*)?[☐☒]?\s*(\d+)\.\s+(.*)$/);
|
|
2772
|
+
if (m && Number(m[1]) === n && squash(m[2]).startsWith(lab)) return i;
|
|
2773
|
+
}
|
|
2774
|
+
return -1;
|
|
2775
|
+
}
|
|
2776
|
+
function checked(lines3, line) {
|
|
2777
|
+
return line >= 0 && /^\s*☒/.test(lines3[line]);
|
|
2778
|
+
}
|
|
2779
|
+
function singleCursor(lines3, q) {
|
|
2780
|
+
const n = q.options.length;
|
|
2781
|
+
const start = questionStart(lines3);
|
|
2782
|
+
if (start < 0) return -1;
|
|
2783
|
+
for (let i = start; i < lines3.length; i++) {
|
|
2784
|
+
const l = lines3[i];
|
|
2785
|
+
const m = l.match(new RegExp(`^\\s*${CURSOR}\\s*(\\d+)\\.\\s+(.*)$`));
|
|
2786
|
+
if (m) {
|
|
2787
|
+
const k = Number(m[1]) - 1;
|
|
2788
|
+
if (k >= 0 && k < n) return squash(m[2]).startsWith(squash(q.options[k].label).slice(0, 6)) ? k : -1;
|
|
2789
|
+
if (k === n) return /^Typesomething/.test(squash(m[2])) ? n : -1;
|
|
2790
|
+
return -1;
|
|
2791
|
+
}
|
|
2792
|
+
if (new RegExp(`^\\s*${CURSOR}\\s*(Next|Submit)\\s*$`).test(l)) return n + 1;
|
|
2793
|
+
}
|
|
2794
|
+
return -1;
|
|
2795
|
+
}
|
|
2796
|
+
async function move(io, delta) {
|
|
2797
|
+
const key = delta > 0 ? KEY_DOWN : KEY_UP;
|
|
2798
|
+
for (let i = 0; i < Math.abs(delta); i++) {
|
|
2799
|
+
io.write(key);
|
|
2800
|
+
await io.sleep(ARROW_GAP_MS);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
async function answerQuestions(io, questions, selections) {
|
|
2804
|
+
if (questions.length === 0 || selections.length !== questions.length) return "not_waiting";
|
|
2805
|
+
for (let qi = 0; qi < questions.length; qi++) {
|
|
2806
|
+
const sel = selections[qi];
|
|
2807
|
+
const q = questions[qi];
|
|
2808
|
+
if (sel.length === 0 || sel.some((i) => !Number.isInteger(i) || i < 0 || i >= q.options.length)) return "not_waiting";
|
|
2809
|
+
if (q.multiSelect !== true && sel.length !== 1) return "not_waiting";
|
|
2810
|
+
}
|
|
2811
|
+
if (!await settled(io, (l) => onQuestion(l, questions[0]))) return "not_waiting";
|
|
2812
|
+
if (questions.some((q) => q.multiSelect === true) && (!io.cursorLine || io.cursorLine() === null)) return "unsupported";
|
|
2813
|
+
let wrote = false;
|
|
2814
|
+
const fail = () => wrote ? "partial" : "not_waiting";
|
|
2815
|
+
for (let qi = 0; qi < questions.length; qi++) {
|
|
2816
|
+
const q = questions[qi];
|
|
2817
|
+
const n = q.options.length;
|
|
2818
|
+
const sel = [...selections[qi]].sort((a, b) => a - b);
|
|
2819
|
+
if (!await waitUntil(io, (l) => onQuestion(l, q))) return fail();
|
|
2820
|
+
if (q.multiSelect === true) {
|
|
2821
|
+
const lines0 = io.screen();
|
|
2822
|
+
for (let i = 0; i < n; i++) if (checked(lines0, optionLine(lines0, i + 1, q.options[i].label))) return fail();
|
|
2823
|
+
const first = optionLine(lines0, 1, q.options[0].label);
|
|
2824
|
+
if (first < 0 || io.cursorLine?.() !== first) return fail();
|
|
2825
|
+
let cur = 0;
|
|
2826
|
+
for (const t of sel) {
|
|
2827
|
+
await move(io, t - cur);
|
|
2828
|
+
cur = t;
|
|
2829
|
+
const onTarget = await waitUntil(io, (l) => {
|
|
2830
|
+
const ln = optionLine(l, t + 1, q.options[t].label);
|
|
2831
|
+
return ln >= 0 && io.cursorLine?.() === ln;
|
|
2832
|
+
});
|
|
2833
|
+
if (!onTarget) return fail();
|
|
2834
|
+
io.write(KEY_ENTER);
|
|
2835
|
+
wrote = true;
|
|
2836
|
+
if (!await waitUntil(io, (l) => checked(l, optionLine(l, t + 1, q.options[t].label)))) return fail();
|
|
2837
|
+
}
|
|
2838
|
+
await move(io, n + 1 - cur);
|
|
2839
|
+
if (!await waitUntil(io, (l) => questionStart(l) >= 0 && new RegExp(`^\\s*${CURSOR}\\s*(Submit|Next)\\s*$`, "m").test(joined(l.slice(questionStart(l)))))) return fail();
|
|
2840
|
+
io.write(KEY_ENTER);
|
|
2841
|
+
wrote = true;
|
|
2842
|
+
} else {
|
|
2843
|
+
const t = sel[0];
|
|
2844
|
+
const cur = singleCursor(io.screen(), q);
|
|
2845
|
+
if (cur < 0) return fail();
|
|
2846
|
+
await move(io, t - cur);
|
|
2847
|
+
if (!await waitUntil(io, (l) => singleCursor(l, q) === t && optionLine(l, t + 1, q.options[t].label) >= 0)) return fail();
|
|
2848
|
+
io.write(KEY_ENTER);
|
|
2849
|
+
wrote = true;
|
|
2850
|
+
}
|
|
2851
|
+
const next = questions[qi + 1];
|
|
2852
|
+
const moved = await waitUntil(io, (l) => next ? onQuestion(l, next) : isReviewPage(l) || !isQuestionDialog(l));
|
|
2853
|
+
if (!moved) return fail();
|
|
2854
|
+
}
|
|
2855
|
+
const s2 = io.screen();
|
|
2856
|
+
if (!isReviewPage(s2)) return isQuestionDialog(s2) ? fail() : "done";
|
|
2857
|
+
if (!new RegExp(`^\\s*${CURSOR}\\s*1\\.\\s+Submit answers`, "m").test(joined(s2))) return fail();
|
|
2858
|
+
io.write(KEY_ENTER);
|
|
2859
|
+
return await waitUntil(io, (l) => !isReviewPage(l) && !isQuestionDialog(l)) ? "done" : "partial";
|
|
2860
|
+
}
|
|
2861
|
+
function stillWaiting(lines3, probe) {
|
|
2862
|
+
if (probe.questions && probe.questions.length > 0) return probe.questions.some((q) => onQuestion(lines3, q)) || isReviewPage(lines3);
|
|
2863
|
+
return isPermissionDialog(lines3, probe.toolName, probe.fingerprint ?? "");
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
// src/wrap/screen.ts
|
|
2867
|
+
var ESC = "\x1B";
|
|
2868
|
+
var PASTE_BEGIN = ESC + "[200~";
|
|
2869
|
+
var PASTE_END = ESC + "[201~";
|
|
2870
|
+
function sanitize(text) {
|
|
2871
|
+
return String(text).replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
|
|
2872
|
+
}
|
|
2873
|
+
var MAX_INJECT_CHARS = 800;
|
|
2874
|
+
var TEXT_TOO_LONG = "text_too_long";
|
|
2875
|
+
function oneLine(text) {
|
|
2876
|
+
return sanitize(text).replace(/\s*\n\s*/g, " ").trim();
|
|
2877
|
+
}
|
|
2878
|
+
function tooLongToInject(text) {
|
|
2879
|
+
return oneLine(text).length > MAX_INJECT_CHARS;
|
|
2880
|
+
}
|
|
2881
|
+
function prepare(text) {
|
|
2882
|
+
return " " + oneLine(text);
|
|
2883
|
+
}
|
|
2884
|
+
var TRUST_RE = /trust (the files in )?this folder|Yes, I trust this folder/i;
|
|
2885
|
+
var PLACEHOLDER_RE = /^Try ".*"$/;
|
|
2886
|
+
function isTerminalReport(s2) {
|
|
2887
|
+
if (!s2) return true;
|
|
2888
|
+
const rest = s2.replace(/\x1b\[[IO]/g, "").replace(/\x1b\[\d+;\d+R/g, "").replace(/\x1b\[[?>][\d;]*\$?[a-zA-Z]/g, "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1bP[^\x1b]*\x1b\\/g, "");
|
|
2889
|
+
return rest === "";
|
|
2890
|
+
}
|
|
2891
|
+
var PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
|
|
2892
|
+
function inspectLines(lines3, undimmed) {
|
|
2893
|
+
const text = lines3.join("\n");
|
|
2894
|
+
const trust = TRUST_RE.test(text);
|
|
2895
|
+
const permission = PERMISSION_RE.test(text);
|
|
2896
|
+
const rules = [];
|
|
2897
|
+
lines3.forEach((l, i) => {
|
|
2898
|
+
if (/^─{20,}/.test(l.trim())) rules.push(i);
|
|
2899
|
+
});
|
|
2900
|
+
let input = null;
|
|
2901
|
+
if (rules.length >= 2) {
|
|
2902
|
+
const a = rules[rules.length - 2];
|
|
2903
|
+
const b = rules[rules.length - 1];
|
|
2904
|
+
const src = undimmed && undimmed.length === lines3.length ? undimmed : lines3;
|
|
2905
|
+
input = src.slice(a + 1, b).map((l) => l.trimEnd()).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
|
|
2906
|
+
}
|
|
2907
|
+
const v = { permission, input };
|
|
2908
|
+
if (trust) v.trust = true;
|
|
2909
|
+
if (input !== null && PLACEHOLDER_RE.test(input.trim())) v.placeholder = true;
|
|
2910
|
+
return v;
|
|
2911
|
+
}
|
|
2912
|
+
function boxMatches(view, body) {
|
|
2913
|
+
if (view.input === null || view.permission) return false;
|
|
2914
|
+
const flat2 = (x) => x.replace(/\s+/g, "");
|
|
2915
|
+
return flat2(view.input) === flat2(body) || /\[Pasted text #\d+/.test(view.input);
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
// src/wrap/screenRules.ts
|
|
2919
|
+
var Q_RULE_RE = /^(?:─{20,}|-{20,})$/;
|
|
2920
|
+
var Q_TRUST_RE = /Do you trust the files in this folder\?/;
|
|
2921
|
+
var Q_PERMISSION_RE = /Permission Required|Apply this change\?|\d\. Allow once|\d\. Allow for this session/;
|
|
2922
|
+
var Q_LOGIN_RE = /Sign in to continue|Login with QoderCN|How would you like to authenticate/;
|
|
2923
|
+
var Q_BUSY_RE = /\(esc to cancel\b/;
|
|
2924
|
+
var Q_PLACEHOLDER_RE = /^Type your message or @\S*$/;
|
|
2925
|
+
var Q_FOOTER_RE = /· ctx\b/;
|
|
2926
|
+
function bottomRegion(lines3, rules) {
|
|
2927
|
+
const anchor = rules.length >= 3 ? rules[rules.length - 3] : rules[rules.length - 1];
|
|
2928
|
+
return lines3.slice(Math.max(0, anchor - 1)).join("\n");
|
|
2929
|
+
}
|
|
2930
|
+
function qoderCnRule(lines3) {
|
|
2931
|
+
const rules = [];
|
|
2932
|
+
lines3.forEach((l, i) => {
|
|
2933
|
+
if (Q_RULE_RE.test(l.trim())) rules.push(i);
|
|
2934
|
+
});
|
|
2935
|
+
if (rules.length === 0) return { permission: false, input: null };
|
|
2936
|
+
const text = bottomRegion(lines3, rules);
|
|
2937
|
+
if (Q_TRUST_RE.test(text)) return { permission: false, input: null, trust: true };
|
|
2938
|
+
if (Q_LOGIN_RE.test(text)) return { permission: false, input: null, dialog: "login" };
|
|
2939
|
+
const permission = Q_PERMISSION_RE.test(text);
|
|
2940
|
+
let input = null;
|
|
2941
|
+
if (rules.length >= 2) {
|
|
2942
|
+
const a = rules[rules.length - 2];
|
|
2943
|
+
const b = rules[rules.length - 1];
|
|
2944
|
+
const box = lines3.slice(a + 1, b).map((l) => l.trimEnd());
|
|
2945
|
+
if (box.length > 0 && /^\s*>/.test(box[0])) input = box.join("\n").replace(/^\s*>\s?/, "").trim();
|
|
2946
|
+
}
|
|
2947
|
+
const v = { permission, input };
|
|
2948
|
+
if (Q_BUSY_RE.test(text)) v.busy = true;
|
|
2949
|
+
if (input !== null && Q_PLACEHOLDER_RE.test(input)) v.placeholder = true;
|
|
2950
|
+
else if (input !== null && input.startsWith("/")) v.dialog = "slash-menu";
|
|
2951
|
+
if (!v.dialog && lines3.slice(rules[rules.length - 1] + 1).some((l) => l.trim() !== "" && !Q_FOOTER_RE.test(l))) v.dialog = "popup";
|
|
2952
|
+
return v;
|
|
2953
|
+
}
|
|
2954
|
+
function textRejectFor(tool) {
|
|
2955
|
+
if (tool !== "qoder_cn") return void 0;
|
|
2956
|
+
return (text) => /^[/!]/.test(oneLine(text)) ? "slash_command" : null;
|
|
2957
|
+
}
|
|
2958
|
+
var RULES = {
|
|
2959
|
+
claude_code: inspectLines,
|
|
2960
|
+
qoder_cn: qoderCnRule
|
|
2961
|
+
};
|
|
2962
|
+
function screenRuleFor(tool) {
|
|
2963
|
+
return RULES[tool] ?? (() => null);
|
|
2964
|
+
}
|
|
2965
|
+
var INJECT_READY = {
|
|
2966
|
+
claude_code: true,
|
|
2967
|
+
qoder_cn: true
|
|
2968
|
+
};
|
|
2969
|
+
function injectReady(tool) {
|
|
2970
|
+
return INJECT_READY[tool] === true;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
// src/wrap/qoderPermBox.ts
|
|
2974
|
+
var TITLE_RE = /^\s*Permission Required\s*$/;
|
|
2975
|
+
var TOOL_RE = /^\s*Tool:\s*(\S.*?)\s*$/;
|
|
2976
|
+
var FILE_RE = /^\s*File:\s*(\S.*?)\s*$/;
|
|
2977
|
+
var OPTION_RE = /^(\s*)(?:([❯>])\s*)?(\d+)\.\s+(\S.*?)\s*$/;
|
|
2978
|
+
var HINT_RE = /\b(?:esc|enter|tab|ctrl|shift)\b|[↑↓←→]|\bto (?:cancel|select|confirm|navigate|choose)\b|按\s*\S+\s*(?:取消|确认|选择)/i;
|
|
2979
|
+
function squash2(s2) {
|
|
2980
|
+
return s2.replace(/[\s─-╿|+]+/g, "");
|
|
2981
|
+
}
|
|
2982
|
+
function slash(p) {
|
|
2983
|
+
return p.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
2984
|
+
}
|
|
2985
|
+
function qoderBoxFingerprint(toolName, input) {
|
|
2986
|
+
const a = input && typeof input === "object" ? input : {};
|
|
2987
|
+
const s2 = (v) => typeof v === "string" ? v : "";
|
|
2988
|
+
if (toolName === "Bash") {
|
|
2989
|
+
const c = squash2(s2(a.command));
|
|
2990
|
+
return c ? `cmd:${c}` : "";
|
|
2991
|
+
}
|
|
2992
|
+
if (["Write", "Edit", "MultiEdit", "Read", "NotebookEdit"].includes(toolName)) {
|
|
2993
|
+
const p = s2(a.file_path) || s2(a.notebook_path);
|
|
2994
|
+
return p.trim() ? `file:${slash(p.trim())}` : "";
|
|
2995
|
+
}
|
|
2996
|
+
return "";
|
|
2997
|
+
}
|
|
2998
|
+
function parseQoderPermissionBox(lines3) {
|
|
2999
|
+
const view = qoderCnRule(lines3);
|
|
3000
|
+
if (!view || !view.permission || view.trust) return null;
|
|
3001
|
+
let title = -1;
|
|
3002
|
+
for (let i = lines3.length - 1; i >= 0; i--) {
|
|
3003
|
+
if (TITLE_RE.test(lines3[i])) {
|
|
3004
|
+
title = i;
|
|
3005
|
+
break;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
if (title < 0) return null;
|
|
3009
|
+
const region = lines3.slice(title);
|
|
3010
|
+
let tool = "";
|
|
3011
|
+
let file3;
|
|
3012
|
+
for (const l of region) {
|
|
3013
|
+
const m = l.match(TOOL_RE);
|
|
3014
|
+
if (m && !tool) tool = m[1];
|
|
3015
|
+
const f = l.match(FILE_RE);
|
|
3016
|
+
if (f && file3 === void 0) file3 = f[1];
|
|
3017
|
+
}
|
|
3018
|
+
if (!tool) return null;
|
|
3019
|
+
let best = null;
|
|
3020
|
+
let cur = [];
|
|
3021
|
+
const close = () => {
|
|
3022
|
+
if (cur.length >= 2) best = cur;
|
|
3023
|
+
cur = [];
|
|
3024
|
+
};
|
|
3025
|
+
region.forEach((l, idx) => {
|
|
3026
|
+
const m = l.match(OPTION_RE);
|
|
3027
|
+
if (m) {
|
|
3028
|
+
const n = Number(m[3]);
|
|
3029
|
+
if (n === 1) close();
|
|
3030
|
+
else if (cur.length === 0 || n !== cur[cur.length - 1].n + 1) {
|
|
3031
|
+
close();
|
|
3032
|
+
return;
|
|
3033
|
+
}
|
|
3034
|
+
const col = l.length - l.trimStart().length + (m[2] ? m[2].length : 0);
|
|
3035
|
+
cur.push({ n, label: m[4], selected: !!m[2], col: l.indexOf(m[4], col), rows: [idx] });
|
|
3036
|
+
return;
|
|
3037
|
+
}
|
|
3038
|
+
const last = cur[cur.length - 1];
|
|
3039
|
+
if (last && l.trim() !== "" && l.length - l.trimStart().length >= last.col && !HINT_RE.test(l)) {
|
|
3040
|
+
last.label = `${last.label} ${l.trim()}`;
|
|
3041
|
+
last.rows.push(idx);
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
close();
|
|
3045
|
+
});
|
|
3046
|
+
close();
|
|
3047
|
+
const opts = best;
|
|
3048
|
+
if (!opts || opts.filter((o) => o.selected).length !== 1) return null;
|
|
3049
|
+
const optRows = new Set(opts.flatMap((o) => o.rows));
|
|
3050
|
+
return {
|
|
3051
|
+
tool,
|
|
3052
|
+
...file3 !== void 0 ? { file: file3 } : {},
|
|
3053
|
+
options: opts.map(({ n, label, selected }) => ({ n, label, selected })),
|
|
3054
|
+
body: squash2(region.filter((_, i) => !optRows.has(i)).join("\n"))
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
function boxIsFor(box, toolName, fingerprint2) {
|
|
3058
|
+
if (!box || box.tool !== toolName) return false;
|
|
3059
|
+
if (!fingerprint2) return true;
|
|
3060
|
+
if (fingerprint2.startsWith("cmd:")) return box.body.includes(fingerprint2.slice(4));
|
|
3061
|
+
if (fingerprint2.startsWith("file:")) {
|
|
3062
|
+
const full = fingerprint2.slice(5);
|
|
3063
|
+
const shown = box.file ? slash(box.file.trim()) : "";
|
|
3064
|
+
return !!shown && (full === shown || full.endsWith(shown.startsWith("/") ? shown : `/${shown}`));
|
|
3065
|
+
}
|
|
3066
|
+
return false;
|
|
3067
|
+
}
|
|
3068
|
+
function sameLabels(box, labels) {
|
|
3069
|
+
return box.options.length === labels.length && box.options.every((o, i) => o.label === labels[i]);
|
|
3070
|
+
}
|
|
3071
|
+
var QoderBoxTracker = class {
|
|
3072
|
+
#id = 0;
|
|
3073
|
+
#sig = null;
|
|
3074
|
+
#box = null;
|
|
3075
|
+
update(lines3) {
|
|
3076
|
+
const box = lines3 ? parseQoderPermissionBox(lines3) : null;
|
|
3077
|
+
const sig = box ? JSON.stringify([box.tool, box.file ?? "", box.body, box.options.map((o) => o.label)]) : null;
|
|
3078
|
+
if (sig !== null && sig !== this.#sig) this.#id++;
|
|
3079
|
+
this.#sig = sig;
|
|
3080
|
+
this.#box = box;
|
|
3081
|
+
}
|
|
3082
|
+
current() {
|
|
3083
|
+
return this.#box ? { id: this.#id, box: this.#box } : null;
|
|
3084
|
+
}
|
|
3085
|
+
};
|
|
3086
|
+
function snap(io) {
|
|
3087
|
+
io.track.update(io.screen());
|
|
3088
|
+
return io.track.current();
|
|
3089
|
+
}
|
|
3090
|
+
var READ_POLL_MS = 250;
|
|
3091
|
+
var READ_WAIT_MS = 2500;
|
|
3092
|
+
async function readQoderBox(io, toolName, fingerprint2, waitMs = READ_WAIT_MS) {
|
|
3093
|
+
const loops = Math.max(2, Math.ceil(waitMs / READ_POLL_MS) + 1);
|
|
3094
|
+
let prev = null;
|
|
3095
|
+
let prevOther = null;
|
|
3096
|
+
for (let i = 0; i < loops; i++) {
|
|
3097
|
+
const cur = snap(io);
|
|
3098
|
+
if (cur && boxIsFor(cur.box, toolName, fingerprint2)) {
|
|
3099
|
+
prevOther = null;
|
|
3100
|
+
const labels = cur.box.options.map((o) => o.label);
|
|
3101
|
+
const key = JSON.stringify([cur.id, labels]);
|
|
3102
|
+
if (key === prev) return { labels, box: cur.id };
|
|
3103
|
+
prev = key;
|
|
3104
|
+
} else {
|
|
3105
|
+
prev = null;
|
|
3106
|
+
if (cur && prevOther === cur.id) return null;
|
|
3107
|
+
prevOther = cur ? cur.id : null;
|
|
3108
|
+
}
|
|
3109
|
+
if (i < loops - 1) await io.sleep(READ_POLL_MS);
|
|
3110
|
+
}
|
|
3111
|
+
return null;
|
|
3112
|
+
}
|
|
3113
|
+
var KEY_GAP_MS = 120;
|
|
3114
|
+
var CONFIRM_MS2 = 3e3;
|
|
3115
|
+
var SETTLE_MS2 = 300;
|
|
3116
|
+
async function waitUntil2(io, ok, ms = CONFIRM_MS2) {
|
|
3117
|
+
const loops = Math.max(1, Math.ceil(ms / 150));
|
|
3118
|
+
for (let i = 0; i < loops; i++) {
|
|
3119
|
+
if (ok()) return true;
|
|
3120
|
+
await io.sleep(150);
|
|
3121
|
+
}
|
|
3122
|
+
return ok();
|
|
3123
|
+
}
|
|
3124
|
+
var dropAnswerKeys = (s2) => /[\r\n]/.test(s2) || /^[0-9]+$/.test(s2);
|
|
3125
|
+
async function pressQoderOption(io, toolName, fingerprint2, labels, n, boxId) {
|
|
3126
|
+
if (!Number.isInteger(n) || n < 1 || n > labels.length) return "not_waiting";
|
|
3127
|
+
const box = () => {
|
|
3128
|
+
const c = snap(io);
|
|
3129
|
+
return c && c.id === boxId && boxIsFor(c.box, toolName, fingerprint2) && sameLabels(c.box, labels) ? c.box : null;
|
|
3130
|
+
};
|
|
3131
|
+
if (!box()) return "not_waiting";
|
|
3132
|
+
await io.sleep(SETTLE_MS2);
|
|
3133
|
+
const b0 = box();
|
|
3134
|
+
if (!b0) return "not_waiting";
|
|
3135
|
+
const from = b0.options.findIndex((o) => o.selected) + 1;
|
|
3136
|
+
if (from < 1) return "not_waiting";
|
|
3137
|
+
io.hold?.(true);
|
|
3138
|
+
let drop;
|
|
3139
|
+
try {
|
|
3140
|
+
const delta = n - from;
|
|
3141
|
+
const key = delta > 0 ? KEY_DOWN : KEY_UP;
|
|
3142
|
+
const back = delta > 0 ? KEY_UP : KEY_DOWN;
|
|
3143
|
+
let moved = 0;
|
|
3144
|
+
for (let i = 0; i < Math.abs(delta); i++) {
|
|
3145
|
+
io.write(key);
|
|
3146
|
+
moved++;
|
|
3147
|
+
await io.sleep(KEY_GAP_MS);
|
|
3148
|
+
}
|
|
3149
|
+
const onTarget = await waitUntil2(io, () => box()?.options[n - 1].selected === true);
|
|
3150
|
+
if (!onTarget) {
|
|
3151
|
+
if (moved === 0) return "not_waiting";
|
|
3152
|
+
for (let i = 0; i < moved; i++) {
|
|
3153
|
+
io.write(back);
|
|
3154
|
+
await io.sleep(KEY_GAP_MS);
|
|
3155
|
+
}
|
|
3156
|
+
return await waitUntil2(io, () => box()?.options[from - 1].selected === true) ? "restored" : "cursor_moved";
|
|
3157
|
+
}
|
|
3158
|
+
io.write(KEY_ENTER);
|
|
3159
|
+
if (!await waitUntil2(io, () => !box())) return "unconfirmed";
|
|
3160
|
+
drop = dropAnswerKeys;
|
|
3161
|
+
return "done";
|
|
3162
|
+
} finally {
|
|
3163
|
+
io.hold?.(false, drop);
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
async function applyQoderAnswer(req, io) {
|
|
3167
|
+
const a = req.answer;
|
|
3168
|
+
if (a.kind !== "qoder-read" && a.kind !== "qoder-key") return null;
|
|
3169
|
+
const id = req.instructionId;
|
|
3170
|
+
if (req.expireAt > 0 && Date.now() > req.expireAt) return { instructionId: id, status: "rejected", reason: "expired" };
|
|
3171
|
+
if (!io.screen()) return { instructionId: id, status: "rejected", reason: "no_screen" };
|
|
3172
|
+
if (a.kind === "qoder-read") {
|
|
3173
|
+
const r2 = await readQoderBox(io, a.toolName, a.fingerprint ?? "");
|
|
3174
|
+
return r2 ? { instructionId: id, status: "started", mode: "window", options: r2.labels, box: r2.box } : { instructionId: id, status: "rejected", reason: "not_waiting" };
|
|
3175
|
+
}
|
|
3176
|
+
let r;
|
|
3177
|
+
try {
|
|
3178
|
+
r = await pressQoderOption(io, a.toolName, a.fingerprint ?? "", a.labels, a.n, a.box);
|
|
3179
|
+
} catch {
|
|
3180
|
+
r = "unconfirmed";
|
|
3181
|
+
}
|
|
3182
|
+
if (r === "done") return { instructionId: id, status: "started", mode: "window" };
|
|
3183
|
+
if (r === "not_waiting" || r === "restored" || r === "cursor_moved") return { instructionId: id, status: "rejected", reason: r };
|
|
3184
|
+
return { instructionId: id, status: "failed", mode: "window", reason: "unconfirmed" };
|
|
3185
|
+
}
|
|
3186
|
+
|
|
2605
3187
|
// src/daemon/gate/coordinator.ts
|
|
2606
3188
|
var REMEMBER_TOOLS = /* @__PURE__ */ new Set([
|
|
2607
3189
|
"claude_code",
|
|
@@ -2620,7 +3202,7 @@ var REMEMBER_SCOPE = {
|
|
|
2620
3202
|
};
|
|
2621
3203
|
function rememberScopeOf(tool, ui) {
|
|
2622
3204
|
if (tool === "claude_code") return "all";
|
|
2623
|
-
if (tool === "qoder_cn" && ui === "tui") return "local";
|
|
3205
|
+
if ((tool === "qoder_cn" || tool === "qoder") && ui === "tui") return "local";
|
|
2624
3206
|
return "session";
|
|
2625
3207
|
}
|
|
2626
3208
|
function pickRememberSuggestion(suggestions, opts = {}) {
|
|
@@ -2685,12 +3267,14 @@ function rememberUpdatesOf(rec) {
|
|
|
2685
3267
|
function windowOptions(tool, toolName, hook, viaGate, host = {}) {
|
|
2686
3268
|
const ui = host.ui;
|
|
2687
3269
|
if (!viaGate && hook.deskbuddy_options !== void 0)
|
|
2688
|
-
return checkOptions(hook.deskbuddy_options, tool === "workbuddy" ? { anyKinds: true
|
|
3270
|
+
return checkOptions(hook.deskbuddy_options, tool === "workbuddy" ? { anyKinds: true } : {});
|
|
2689
3271
|
if (tool === "claude_code")
|
|
2690
3272
|
return claudeOptions(toolName, hook.permission_suggestions, { autoModeAllowed: host.ccAutoMode === "on", permissionMode: hook.permission_mode });
|
|
2691
3273
|
if (tool === "opencode") return opencodeOptions(hook.oc_permission, hook.oc_always, hook.oc_child);
|
|
2692
|
-
|
|
2693
|
-
if (
|
|
3274
|
+
const isQoder = tool === "qoder_cn" || tool === "qoder";
|
|
3275
|
+
if (isQoder && ui === "tui") return qoderCnTuiOptions(toolName, hook.tool_input, hook.permission_suggestions);
|
|
3276
|
+
if (isQoder && ui === "gui") return qoderCnGuiOptions(hook.permission_suggestions, host.lang);
|
|
3277
|
+
if (tool === "qwenwork" && !viaGate) return qwenworkOptions(toolName, host.lang);
|
|
2694
3278
|
if (tool === "workbuddy" && hook.deskbuddy_cb_product !== void 0)
|
|
2695
3279
|
return codebuddyOptions(toolName, hook.tool_input, hook.deskbuddy_cb_product, { truncated: hook.deskbuddy_cb_truncated === true, sandbox: hook.deskbuddy_cb_box === "sandbox" });
|
|
2696
3280
|
return null;
|
|
@@ -2930,7 +3514,33 @@ var ApprovalCoordinator = class {
|
|
|
2930
3514
|
const cardSummary = ownSummary || summary2;
|
|
2931
3515
|
const inProject = kind === "edit" && !qv && !trust && !!cwd && editInProject(toolName, hook.tool_input, cwdForms(cwd), { real: realpathNearest });
|
|
2932
3516
|
const remember = qv || trust ? null : tool === "codex" ? codexRemember(hook.deskbuddy_remember) : REMEMBER_TOOLS.has(tool) ? pickRememberSuggestion(hook.permission_suggestions, { edit: EDIT_TOOLS2.has(toolName) || kind === "edit", scope: rememberScopeOf(tool, host.ui) }) : null;
|
|
2933
|
-
|
|
3517
|
+
let appSet = null;
|
|
3518
|
+
const appByInput = !(typeof hook.tool_use_id === "string" && hook.tool_use_id);
|
|
3519
|
+
const twin = appByInput && this.#d.store.pendingForSession(sessionId).some((r) => r.mode === "window" && r.fingerprint === fingerprint(toolName, hook.tool_input));
|
|
3520
|
+
if (!qv && !trust && !twin && viaGate && (tool === "qoder_cn" || tool === "qoder") && host.ui !== "tui" && this.#d.qoderApp) {
|
|
3521
|
+
appSet = await this.#d.qoderApp.options(tool, hook, signal).catch(() => null);
|
|
3522
|
+
if (signal.aborted) return none;
|
|
3523
|
+
}
|
|
3524
|
+
let optSet = qv || trust ? null : appSet ?? windowOptions(tool, toolName, hook, viaGate, host);
|
|
3525
|
+
let screenBox;
|
|
3526
|
+
const sameToolPending = this.#d.store.pending().some((r) => r.mode === "window" && r.sessionId === sessionId && this.#windows.get(r.approvalId)?.toolName === toolName);
|
|
3527
|
+
if (!qv && !trust && tool === "qoder_cn" && host.ui === "tui" && this.#d.qoderScreen && !sameToolPending) {
|
|
3528
|
+
const fp = qoderBoxFingerprint(toolName, hook.tool_input);
|
|
3529
|
+
let got = null;
|
|
3530
|
+
try {
|
|
3531
|
+
got = await this.#d.qoderScreen.read(sessionId, toolName, fp);
|
|
3532
|
+
} catch {
|
|
3533
|
+
got = null;
|
|
3534
|
+
}
|
|
3535
|
+
const taken = !!got && this.#d.store.pending().some((r) => r.mode === "window" && r.sessionId === sessionId && r.screenBox?.box === got?.box);
|
|
3536
|
+
const s2 = got && !taken ? qoderCnScreenOptions(got.labels, optSet) : null;
|
|
3537
|
+
if (s2 && got) {
|
|
3538
|
+
optSet = s2;
|
|
3539
|
+
screenBox = { toolName, fingerprint: fp, labels: got.labels, box: got.box };
|
|
3540
|
+
} else if (taken) this.#d.log(`Qoder \u6388\u6743\u6846\u5B9E\u4F8B ${got?.box} \u5DF2\u5C5E\u4E8E\u522B\u7684\u5F85\u6279\u51C6\u5361\uFF0C\u6309\u5DE5\u5177\u67E5\u8868`);
|
|
3541
|
+
else if (got) this.#d.log(`Qoder \u6388\u6743\u6846\u539F\u6587\u6620\u5C04\u4E0D\u8FC7\u81EA\u68C0\uFF0C\u6309\u5DE5\u5177\u67E5\u8868\uFF1A${got.labels.join(" / ").slice(0, 200)}`);
|
|
3542
|
+
if (signal.aborted) return none;
|
|
3543
|
+
}
|
|
2934
3544
|
const rec = {
|
|
2935
3545
|
approvalId: randomUUID(),
|
|
2936
3546
|
runId: "",
|
|
@@ -2945,6 +3555,7 @@ var ApprovalCoordinator = class {
|
|
|
2945
3555
|
createdAt: this.#now(),
|
|
2946
3556
|
...remember ? { remember: remember.text, rememberUpdates: remember.updates } : {},
|
|
2947
3557
|
...optSet ? { options: optSet.options, optionActions: optSet.actions, ...optSet.pcAllowAmbiguous ? { pcAllowAmbiguous: true } : {} } : {},
|
|
3558
|
+
...screenBox ? { screenBox } : {},
|
|
2948
3559
|
...inProject ? { inProject: true } : {},
|
|
2949
3560
|
...qv ? (() => {
|
|
2950
3561
|
const context = lastAssistantText(hook.transcript_path);
|
|
@@ -3299,6 +3910,10 @@ var ApprovalCoordinator = class {
|
|
|
3299
3910
|
}
|
|
3300
3911
|
rec.optionId = optionId;
|
|
3301
3912
|
}
|
|
3913
|
+
if (action?.screenKey !== void 0 && decision === "allow" && rec.mode === "window") {
|
|
3914
|
+
this.#pressScreenKey(rec, action.screenKey, deviceName, remembered);
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3302
3917
|
const updates = optionId === void 0 ? rememberUpdatesOf(rec) : null;
|
|
3303
3918
|
const always = optionId === void 0 && decision === "allow_always" && rec.mode === "window" && updates !== null;
|
|
3304
3919
|
if (always) remembered = true;
|
|
@@ -3327,7 +3942,20 @@ var ApprovalCoordinator = class {
|
|
|
3327
3942
|
};
|
|
3328
3943
|
if (decision === "allow") {
|
|
3329
3944
|
const perms = action ? action.updatedPermissions : always && updates ? updates : void 0;
|
|
3330
|
-
|
|
3945
|
+
const reply = {
|
|
3946
|
+
decision: "allow",
|
|
3947
|
+
notice,
|
|
3948
|
+
...perms && perms.length > 0 ? { updatedPermissions: perms } : {},
|
|
3949
|
+
...action?.updatedInput ? { updatedInput: action.updatedInput } : {},
|
|
3950
|
+
...opt
|
|
3951
|
+
};
|
|
3952
|
+
const viaApp = action?.qoderApp;
|
|
3953
|
+
if (viaApp && this.#d.qoderApp) {
|
|
3954
|
+
void this.#d.qoderApp.respond(rec.tool, rec.sessionId, viaApp.toolUseId, viaApp.permissionSuggestionId).catch(() => "failed").then((r) => {
|
|
3955
|
+
this.#d.log(`\u7A97\u53E3\u6388\u6743 ${approvalId} \u5207\u6A21\u5F0F\u5EFA\u8BAE\u7ECF App \u56DE\u7B54\uFF1A${r}`);
|
|
3956
|
+
waiter(r === "failed" ? reply : { decision: "none" });
|
|
3957
|
+
});
|
|
3958
|
+
} else waiter(reply);
|
|
3331
3959
|
} else {
|
|
3332
3960
|
waiter({
|
|
3333
3961
|
decision: "deny",
|
|
@@ -3358,6 +3986,48 @@ var ApprovalCoordinator = class {
|
|
|
3358
3986
|
if (decision === "allow") this.#d.resume(rec, "orphan");
|
|
3359
3987
|
}
|
|
3360
3988
|
/** 窗口待批准按「不表态」收掉:本地作废、放掉挂着的 hook(回 none,框留给电脑前的人)。服务端那条已有结果,不撤 */
|
|
3989
|
+
/**
|
|
3990
|
+
* 设备选了只能写键的屏幕选项(spec 2026-10-09 qodercn-tui-screen-options §4.3):
|
|
3991
|
+
* 先把记录标成已决定、取走 waiter(重复下发 / 撤卡扫描都碰不到它),再经窗口通道写键;不管成败 hook 都回 none。
|
|
3992
|
+
* 写不成(框变了 / 电脑上已处理 / hook 已不在)→ 一个键不写,电脑上的框照旧等人,记录改 cancelled
|
|
3993
|
+
*/
|
|
3994
|
+
#pressScreenKey(rec, n, deviceName, remembered) {
|
|
3995
|
+
rec.status = "approved";
|
|
3996
|
+
rec.decidedAt = this.#now();
|
|
3997
|
+
rec.consumed = true;
|
|
3998
|
+
this.#windows.delete(rec.approvalId);
|
|
3999
|
+
const waiter = this.#waiters.get(rec.approvalId);
|
|
4000
|
+
this.#waiters.delete(rec.approvalId);
|
|
4001
|
+
const box = rec.screenBox;
|
|
4002
|
+
const qs = this.#d.qoderScreen;
|
|
4003
|
+
const fail = (why) => {
|
|
4004
|
+
rec.status = "cancelled";
|
|
4005
|
+
this.#d.store.save();
|
|
4006
|
+
this.#d.log(`\u7A97\u53E3\u6388\u6743 ${rec.approvalId} \u8BBE\u5907\u9009\u4E86\u7B2C ${n} \u9879\uFF0C${why}\uFF1A\u6CA1\u6709\u56DE\u8F66\uFF0C\u7535\u8111\u4E0A\u7167\u65E7\u7B49\u4EBA\u5904\u7406`);
|
|
4007
|
+
void this.#cancelRemote(rec.approvalId, "\u8BBE\u5907\u4E0A\u7684\u9009\u62E9\u672A\u751F\u6548\uFF0C\u8BF7\u5230\u7535\u8111\u4E0A\u5904\u7406");
|
|
4008
|
+
};
|
|
4009
|
+
this.#d.store.save();
|
|
4010
|
+
if (!waiter || !box || !qs) {
|
|
4011
|
+
fail(!waiter ? "hook \u5DF2\u4E0D\u5728" : "\u6CA1\u6709\u8BFB\u5C4F\u8BB0\u5F55");
|
|
4012
|
+
waiter?.({ decision: "none" });
|
|
4013
|
+
return;
|
|
4014
|
+
}
|
|
4015
|
+
void qs.press(rec.sessionId, box, n).catch(() => "uncertain").then((r) => {
|
|
4016
|
+
if (r === "done") {
|
|
4017
|
+
this.#d.log(`\u7A97\u53E3\u6388\u6743 ${rec.approvalId} \u8BBE\u5907\u9009\u4E86\u7B2C ${n} \u9879\uFF0C\u5DF2\u5199\u952E\u4F5C\u7B54`);
|
|
4018
|
+
try {
|
|
4019
|
+
;
|
|
4020
|
+
(this.#d.writeNotice ?? writeNotice)(rec.sessionId, windowNotice("allow", rec.summary, deviceName) + (remembered ? "\uFF08\u5DF2\u8BB0\u4F4F\uFF09" : ""));
|
|
4021
|
+
} catch {
|
|
4022
|
+
}
|
|
4023
|
+
} else if (r === "not_written") fail("\u5C4F\u4E0A\u5DF2\u4E0D\u662F\u540C\u4E00\u4E2A\u6388\u6743\u6846");
|
|
4024
|
+
else {
|
|
4025
|
+
this.#d.log(`\u7A97\u53E3\u6388\u6743 ${rec.approvalId} \u8BBE\u5907\u9009\u4E86\u7B2C ${n} \u9879\uFF0C\u5DF2\u5199\u56DE\u8F66\u4F46\u6CA1\u786E\u8BA4\u6846\u5173\u95ED\uFF0C\u7ED3\u679C\u672A\u786E\u8BA4`);
|
|
4026
|
+
void this.#cancelRemote(rec.approvalId, "\u8BBE\u5907\u4E0A\u7684\u9009\u62E9\u53EF\u80FD\u672A\u751F\u6548\uFF0C\u8BF7\u5230\u7535\u8111\u4E0A\u786E\u8BA4");
|
|
4027
|
+
}
|
|
4028
|
+
waiter({ decision: "none" });
|
|
4029
|
+
});
|
|
4030
|
+
}
|
|
3361
4031
|
#dropWindow(rec) {
|
|
3362
4032
|
rec.status = "cancelled";
|
|
3363
4033
|
rec.decidedAt = this.#now();
|
|
@@ -3547,7 +4217,7 @@ function realpathNearest(p) {
|
|
|
3547
4217
|
}
|
|
3548
4218
|
const parent = dirname2(cur);
|
|
3549
4219
|
if (parent === cur) return "";
|
|
3550
|
-
tail3.push(
|
|
4220
|
+
tail3.push(basename5(cur));
|
|
3551
4221
|
cur = parent;
|
|
3552
4222
|
}
|
|
3553
4223
|
return "";
|
|
@@ -3906,8 +4576,8 @@ var SeenIds = class _SeenIds {
|
|
|
3906
4576
|
|
|
3907
4577
|
// src/daemon/qwenworkBridge.ts
|
|
3908
4578
|
import { closeSync as closeSync4, openSync as openSync4, readdirSync as readdirSync8, readFileSync as readFileSync9, readSync as readSync3 } from "fs";
|
|
3909
|
-
import { homedir as
|
|
3910
|
-
import { basename as
|
|
4579
|
+
import { homedir as homedir4 } from "os";
|
|
4580
|
+
import { basename as basename6, dirname as dirname3, join as join16, resolve as resolve3 } from "path";
|
|
3911
4581
|
|
|
3912
4582
|
// src/daemon/dumateWatch.ts
|
|
3913
4583
|
import { execFileSync as execFileSync5, spawn as spawn2 } from "child_process";
|
|
@@ -4759,40 +5429,192 @@ var DumateWatch = class {
|
|
|
4759
5429
|
this.#reconciling = false;
|
|
4760
5430
|
}
|
|
4761
5431
|
}
|
|
4762
|
-
};
|
|
4763
|
-
function runHook(node, input, agentPid) {
|
|
5432
|
+
};
|
|
5433
|
+
function runHook(node, input, agentPid) {
|
|
5434
|
+
return new Promise((resolve10) => {
|
|
5435
|
+
let child;
|
|
5436
|
+
try {
|
|
5437
|
+
child = spawn2(node, [installedHookPath(), "dumate"], {
|
|
5438
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
5439
|
+
env: { ...process.env, DESKBUDDY_AGENT_PID: String(agentPid) }
|
|
5440
|
+
});
|
|
5441
|
+
} catch {
|
|
5442
|
+
resolve10();
|
|
5443
|
+
return;
|
|
5444
|
+
}
|
|
5445
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), HOOK_KILL_MS);
|
|
5446
|
+
timer.unref?.();
|
|
5447
|
+
child.on("error", () => {
|
|
5448
|
+
clearTimeout(timer);
|
|
5449
|
+
resolve10();
|
|
5450
|
+
});
|
|
5451
|
+
child.on("close", () => {
|
|
5452
|
+
clearTimeout(timer);
|
|
5453
|
+
resolve10();
|
|
5454
|
+
});
|
|
5455
|
+
child.stdin.on("error", () => {
|
|
5456
|
+
});
|
|
5457
|
+
child.stdin.end(JSON.stringify(input));
|
|
5458
|
+
});
|
|
5459
|
+
}
|
|
5460
|
+
|
|
5461
|
+
// src/daemon/qwenworkLang.ts
|
|
5462
|
+
import { execFile as execFile2 } from "child_process";
|
|
5463
|
+
import { open } from "fs/promises";
|
|
5464
|
+
import { homedir as homedir3 } from "os";
|
|
5465
|
+
import { join as join15 } from "path";
|
|
5466
|
+
var DATA_DIR = "QwenWorkCN";
|
|
5467
|
+
var MAX_SCAN = 16 * 1024 * 1024;
|
|
5468
|
+
var LANG_RE = /(?<![A-Za-z0-9_:.-])language"(zh-TW|zh|en)"/g;
|
|
5469
|
+
var CACHE_MS = 30 * 1e3;
|
|
5470
|
+
function qwenworkUserDataDir() {
|
|
5471
|
+
const forced = process.env.DESKBUDDY_QWENWORK_DATA;
|
|
5472
|
+
if (forced) return forced;
|
|
5473
|
+
if (process.platform === "darwin") return join15(homedir3(), "Library", "Application Support", DATA_DIR);
|
|
5474
|
+
if (process.platform === "win32") return join15(process.env.APPDATA?.trim() || join15(homedir3(), "AppData", "Roaming"), DATA_DIR);
|
|
5475
|
+
return join15(process.env.XDG_CONFIG_HOME?.trim() || join15(homedir3(), ".config"), DATA_DIR);
|
|
5476
|
+
}
|
|
5477
|
+
async function readAll(p) {
|
|
5478
|
+
let fh;
|
|
5479
|
+
try {
|
|
5480
|
+
fh = await open(p, "r");
|
|
5481
|
+
} catch (e) {
|
|
5482
|
+
return e?.code === "ENOENT" ? "missing" : "error";
|
|
5483
|
+
}
|
|
5484
|
+
try {
|
|
5485
|
+
const size = (await fh.stat()).size;
|
|
5486
|
+
if (size > MAX_SCAN) return "error";
|
|
5487
|
+
const buf = Buffer.alloc(size);
|
|
5488
|
+
let off = 0;
|
|
5489
|
+
while (off < size) {
|
|
5490
|
+
const { bytesRead } = await fh.read(buf, off, size - off, off);
|
|
5491
|
+
if (bytesRead <= 0) break;
|
|
5492
|
+
off += bytesRead;
|
|
5493
|
+
}
|
|
5494
|
+
return buf.subarray(0, off);
|
|
5495
|
+
} catch {
|
|
5496
|
+
return "error";
|
|
5497
|
+
} finally {
|
|
5498
|
+
await fh.close().catch(() => {
|
|
5499
|
+
});
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
function matches(buf) {
|
|
5503
|
+
const out = [];
|
|
5504
|
+
const s2 = buf.toString("latin1");
|
|
5505
|
+
LANG_RE.lastIndex = 0;
|
|
5506
|
+
let m;
|
|
5507
|
+
while (m = LANG_RE.exec(s2)) out.push(m[1]);
|
|
5508
|
+
return out;
|
|
5509
|
+
}
|
|
5510
|
+
function varint(b, at) {
|
|
5511
|
+
let v = 0;
|
|
5512
|
+
for (let i = 0; i < 9; i++) {
|
|
5513
|
+
if (at + i >= b.length) return null;
|
|
5514
|
+
const c = b[at + i];
|
|
5515
|
+
if (i === 8) return [v * 256 + c, at + 9];
|
|
5516
|
+
v = v * 128 + (c & 127);
|
|
5517
|
+
if (!(c & 128)) return [v, at + i + 1];
|
|
5518
|
+
}
|
|
5519
|
+
return null;
|
|
5520
|
+
}
|
|
5521
|
+
function pageLangs(page, first = false) {
|
|
5522
|
+
const h = first ? 100 : 0;
|
|
5523
|
+
if (page.length < h + 8 || page[h] !== 13) return [];
|
|
5524
|
+
const n = page.readUInt16BE(h + 3);
|
|
5525
|
+
const out = [];
|
|
5526
|
+
for (let i = 0; i < n; i++) {
|
|
5527
|
+
const p = h + 8 + 2 * i;
|
|
5528
|
+
if (p + 2 > page.length) break;
|
|
5529
|
+
const cell = page.readUInt16BE(p);
|
|
5530
|
+
if (cell < h + 8 || cell >= page.length) continue;
|
|
5531
|
+
const len = varint(page, cell);
|
|
5532
|
+
const rowid = len ? varint(page, len[1]) : null;
|
|
5533
|
+
if (!len || !rowid) continue;
|
|
5534
|
+
out.push(...matches(page.subarray(rowid[1], Math.min(page.length, rowid[1] + Math.min(len[0], 256)))));
|
|
5535
|
+
}
|
|
5536
|
+
return out;
|
|
5537
|
+
}
|
|
5538
|
+
function langFromWal(wal) {
|
|
5539
|
+
if (!wal || wal.length < 32) return "";
|
|
5540
|
+
const magic = wal.readUInt32BE(0);
|
|
5541
|
+
if (magic !== 931071618 && magic !== 931071619) return "";
|
|
5542
|
+
const pageSize = wal.readUInt32BE(8);
|
|
5543
|
+
if (pageSize < 512 || pageSize > 65536) return "";
|
|
5544
|
+
const salt1 = wal.readUInt32BE(16);
|
|
5545
|
+
const salt2 = wal.readUInt32BE(20);
|
|
5546
|
+
let last = "";
|
|
5547
|
+
for (let off = 32; off + 24 + pageSize <= wal.length; off += 24 + pageSize) {
|
|
5548
|
+
if (wal.readUInt32BE(off + 8) !== salt1 || wal.readUInt32BE(off + 12) !== salt2) break;
|
|
5549
|
+
const found = pageLangs(wal.subarray(off + 24, off + 24 + pageSize), wal.readUInt32BE(off) === 1);
|
|
5550
|
+
if (found.length > 0) last = found[found.length - 1];
|
|
5551
|
+
}
|
|
5552
|
+
return last;
|
|
5553
|
+
}
|
|
5554
|
+
function langFromDb(db) {
|
|
5555
|
+
if (db && db.length === 0) return "";
|
|
5556
|
+
if (!db || db.length < 100 || db.toString("latin1", 0, 16) !== "SQLite format 3\0") return "invalid";
|
|
5557
|
+
const raw = db.readUInt16BE(16);
|
|
5558
|
+
const pageSize = raw === 1 ? 65536 : raw;
|
|
5559
|
+
if (pageSize < 512 || pageSize > 65536 || (pageSize & pageSize - 1) !== 0) return "invalid";
|
|
5560
|
+
const set = /* @__PURE__ */ new Set();
|
|
5561
|
+
for (let off = 0; off + pageSize <= db.length; off += pageSize) for (const v of pageLangs(db.subarray(off, off + pageSize), off === 0)) set.add(v);
|
|
5562
|
+
if (set.size === 1) return [...set][0];
|
|
5563
|
+
return set.size > 1 ? "ambiguous" : "";
|
|
5564
|
+
}
|
|
5565
|
+
function systemLang() {
|
|
4764
5566
|
return new Promise((resolve10) => {
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
5567
|
+
const done = (first) => resolve10(!first ? "" : first.toLowerCase().startsWith("zh") ? "zh" : "en");
|
|
5568
|
+
if (process.platform === "darwin") {
|
|
5569
|
+
execFile2("defaults", ["read", "-g", "AppleLanguages"], { encoding: "utf8", timeout: 3e3 }, (err, out) => {
|
|
5570
|
+
if (err) return done("");
|
|
5571
|
+
const m = /"?([A-Za-z]{2,3}(?:[-_][A-Za-z0-9]+)*)"?/.exec(out);
|
|
5572
|
+
done(m ? m[1] : "");
|
|
4770
5573
|
});
|
|
4771
|
-
} catch {
|
|
4772
|
-
resolve10();
|
|
4773
5574
|
return;
|
|
4774
5575
|
}
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
});
|
|
4781
|
-
child.on("close", () => {
|
|
4782
|
-
clearTimeout(timer);
|
|
4783
|
-
resolve10();
|
|
4784
|
-
});
|
|
4785
|
-
child.stdin.on("error", () => {
|
|
4786
|
-
});
|
|
4787
|
-
child.stdin.end(JSON.stringify(input));
|
|
5576
|
+
try {
|
|
5577
|
+
done(Intl.DateTimeFormat().resolvedOptions().locale || "");
|
|
5578
|
+
} catch {
|
|
5579
|
+
done("");
|
|
5580
|
+
}
|
|
4788
5581
|
});
|
|
4789
5582
|
}
|
|
5583
|
+
async function qwenworkLang(deps2 = {}) {
|
|
5584
|
+
const dir3 = (deps2.dataDir ?? qwenworkUserDataDir)();
|
|
5585
|
+
const read = deps2.readFile ?? readAll;
|
|
5586
|
+
const db = join15(dir3, "data", "agents.db");
|
|
5587
|
+
const wal = await read(`${db}-wal`);
|
|
5588
|
+
if (wal === "error") return "";
|
|
5589
|
+
if (wal !== "missing") {
|
|
5590
|
+
const fromWal = langFromWal(wal);
|
|
5591
|
+
if (fromWal) return fromWal;
|
|
5592
|
+
}
|
|
5593
|
+
const main2 = await read(db);
|
|
5594
|
+
if (main2 === "error") return "";
|
|
5595
|
+
if (main2 !== "missing") {
|
|
5596
|
+
const fromDb = langFromDb(main2);
|
|
5597
|
+
if (fromDb === "ambiguous" || fromDb === "invalid") return "";
|
|
5598
|
+
if (fromDb) return fromDb;
|
|
5599
|
+
}
|
|
5600
|
+
if (process.env.DESKBUDDY_QWENWORK_DATA && !deps2.systemLang) return "";
|
|
5601
|
+
return (deps2.systemLang ?? systemLang)();
|
|
5602
|
+
}
|
|
5603
|
+
function cachedQwenworkLang(now = Date.now) {
|
|
5604
|
+
let hit = null;
|
|
5605
|
+
return async () => {
|
|
5606
|
+
if (hit && now() - hit.at < CACHE_MS) return hit.v;
|
|
5607
|
+
const v = await qwenworkLang();
|
|
5608
|
+
hit = { at: now(), v };
|
|
5609
|
+
return v;
|
|
5610
|
+
};
|
|
5611
|
+
}
|
|
4790
5612
|
|
|
4791
5613
|
// src/daemon/qwenworkBridge.ts
|
|
4792
5614
|
var SUBMIT_TOOL = "qwenwork_task_submit_response";
|
|
4793
5615
|
var DETAIL_TOOL = "qwenwork_task_get_detail";
|
|
4794
5616
|
var SEND_TOOL = "qwenwork_task_send_message";
|
|
4795
|
-
var
|
|
5617
|
+
var POLL_MS2 = 2e3;
|
|
4796
5618
|
var REQUEST_TIMEOUT_MS2 = 5e3;
|
|
4797
5619
|
var EARLY_WINDOW_MS = 10 * 1e3;
|
|
4798
5620
|
var EARLY_RETRY_MS = 1e3;
|
|
@@ -4852,12 +5674,12 @@ function chatIdOf(cwd, root) {
|
|
|
4852
5674
|
if (typeof cwd !== "string" || !cwd) return null;
|
|
4853
5675
|
const dir3 = resolve3(cwd);
|
|
4854
5676
|
if (!sameDir(dirname3(dir3), resolve3(root))) return null;
|
|
4855
|
-
const id =
|
|
5677
|
+
const id = basename6(dir3);
|
|
4856
5678
|
return /^[A-Za-z0-9_-]{1,64}$/.test(id) ? id : null;
|
|
4857
5679
|
}
|
|
4858
5680
|
function transcriptCwdOf(sessionId, home = qwenworkHome()) {
|
|
4859
5681
|
if (!/^[A-Za-z0-9_-]{1,128}$/.test(sessionId)) return null;
|
|
4860
|
-
const base =
|
|
5682
|
+
const base = join16(home, "projects");
|
|
4861
5683
|
let dirs;
|
|
4862
5684
|
try {
|
|
4863
5685
|
dirs = readdirSync8(base);
|
|
@@ -4867,7 +5689,7 @@ function transcriptCwdOf(sessionId, home = qwenworkHome()) {
|
|
|
4867
5689
|
for (const d of dirs) {
|
|
4868
5690
|
let fd;
|
|
4869
5691
|
try {
|
|
4870
|
-
fd = openSync4(
|
|
5692
|
+
fd = openSync4(join16(base, d, `${sessionId}.jsonl`), "r");
|
|
4871
5693
|
} catch {
|
|
4872
5694
|
continue;
|
|
4873
5695
|
}
|
|
@@ -5034,6 +5856,7 @@ var QwenWorkMcp = class {
|
|
|
5034
5856
|
var QwenWorkBridge = class {
|
|
5035
5857
|
#d;
|
|
5036
5858
|
#mcp;
|
|
5859
|
+
#lang;
|
|
5037
5860
|
/** chatId → 正在等设备的那张卡(同一 chatId 同时只推一张) */
|
|
5038
5861
|
#cards = /* @__PURE__ */ new Map();
|
|
5039
5862
|
/** 推过的 (chatId, toolUseId) → 时间:同一道题 / 同一个授权框只推一次 */
|
|
@@ -5045,9 +5868,10 @@ var QwenWorkBridge = class {
|
|
|
5045
5868
|
#stopped = false;
|
|
5046
5869
|
constructor(deps2) {
|
|
5047
5870
|
this.#d = deps2;
|
|
5048
|
-
const read = deps2.readConfig ?? (() => readConfigFile(
|
|
5871
|
+
const read = deps2.readConfig ?? (() => readConfigFile(join16(qwenworkHome(), "mcp-adaptor.config")));
|
|
5049
5872
|
const f = deps2.fetch ?? globalThis.fetch;
|
|
5050
5873
|
this.#mcp = new QwenWorkMcp(read, f, deps2.log);
|
|
5874
|
+
this.#lang = deps2.lang ?? cachedQwenworkLang();
|
|
5051
5875
|
}
|
|
5052
5876
|
#now() {
|
|
5053
5877
|
return this.#d.now ? this.#d.now() : Date.now();
|
|
@@ -5064,7 +5888,7 @@ var QwenWorkBridge = class {
|
|
|
5064
5888
|
async reply(cwd, text, sessionId = "") {
|
|
5065
5889
|
const notSent = (reason, message) => ({ status: "not_sent", reason, message });
|
|
5066
5890
|
if (this.#d.enabled && !this.#d.enabled()) return notSent("tool_unsupported", "\u5343\u95EE\u529E\u516C\u6CA1\u6709\u63A5\u5165");
|
|
5067
|
-
const root = this.#d.workspaceRoot ??
|
|
5891
|
+
const root = this.#d.workspaceRoot ?? join16(homedir4(), ".qwenworkcn", "workspace");
|
|
5068
5892
|
let dir3 = typeof cwd === "string" ? cwd : "";
|
|
5069
5893
|
if (!dir3 && sessionId) dir3 = (this.#d.transcriptCwd ?? transcriptCwdOf)(sessionId) ?? "";
|
|
5070
5894
|
if (!dir3) return notSent("session_gone", "\u7535\u8111\u4E0A\u627E\u4E0D\u5230\u8FD9\u4E2A\u4F1A\u8BDD\u7684\u8BB0\u5F55");
|
|
@@ -5117,7 +5941,7 @@ var QwenWorkBridge = class {
|
|
|
5117
5941
|
toolUseId = typeof det.toolUseId === "string" ? det.toolUseId : "";
|
|
5118
5942
|
if (!toolName) return;
|
|
5119
5943
|
} else return;
|
|
5120
|
-
const root = this.#d.workspaceRoot ??
|
|
5944
|
+
const root = this.#d.workspaceRoot ?? join16(homedir4(), ".qwenworkcn", "workspace");
|
|
5121
5945
|
const chatId = chatIdOf(h.cwd, root);
|
|
5122
5946
|
if (!chatId) {
|
|
5123
5947
|
this.#d.log("\u5343\u95EE\u529E\u516C\uFF1A\u8FD9\u9053\u9898 / \u6388\u6743\u4E0D\u5728\u4EFB\u52A1\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF08\u53EF\u80FD\u662F\u5B50\u4EE3\u7406\uFF09\uFF0C\u4E0D\u63A8\u8BBE\u5907");
|
|
@@ -5135,6 +5959,14 @@ var QwenWorkBridge = class {
|
|
|
5135
5959
|
if (this.#seen.has(key)) return;
|
|
5136
5960
|
this.#seen.set(key, this.#now());
|
|
5137
5961
|
this.#replaceOld(chatId, toolUseId);
|
|
5962
|
+
let lang = "";
|
|
5963
|
+
if (kind === "permission") {
|
|
5964
|
+
try {
|
|
5965
|
+
lang = await this.#lang();
|
|
5966
|
+
} catch {
|
|
5967
|
+
lang = "";
|
|
5968
|
+
}
|
|
5969
|
+
}
|
|
5138
5970
|
const e = await this.#mcp.ensure();
|
|
5139
5971
|
if (!e.ok) {
|
|
5140
5972
|
if (e.why !== "unsupported") this.#d.log(`\u5343\u95EE\u529E\u516C\uFF1A${e.message}\uFF0C\u8FD9\u4E00\u6761\u4E0D\u63A8\u8BBE\u5907`);
|
|
@@ -5148,6 +5980,7 @@ var QwenWorkBridge = class {
|
|
|
5148
5980
|
const gateReq = {
|
|
5149
5981
|
window: true,
|
|
5150
5982
|
tool: "qwenwork",
|
|
5983
|
+
...lang ? { lang } : {},
|
|
5151
5984
|
hook: {
|
|
5152
5985
|
hook_event_name: "PermissionRequest",
|
|
5153
5986
|
session_id: sessionId,
|
|
@@ -5240,7 +6073,7 @@ var QwenWorkBridge = class {
|
|
|
5240
6073
|
}
|
|
5241
6074
|
#ensurePoll() {
|
|
5242
6075
|
if (this.#timer || this.#stopped) return;
|
|
5243
|
-
this.#timer = setInterval(() => void this.poll(), this.#d.pollMs ??
|
|
6076
|
+
this.#timer = setInterval(() => void this.poll(), this.#d.pollMs ?? POLL_MS2);
|
|
5244
6077
|
this.#timer.unref?.();
|
|
5245
6078
|
}
|
|
5246
6079
|
/**
|
|
@@ -5341,21 +6174,21 @@ function sleep(ms) {
|
|
|
5341
6174
|
}
|
|
5342
6175
|
|
|
5343
6176
|
// src/daemon/workbuddyApp/keeper.ts
|
|
5344
|
-
import { execFile as
|
|
6177
|
+
import { execFile as execFile4, spawn as spawn3 } from "child_process";
|
|
5345
6178
|
import { existsSync as existsSync8, mkdirSync as mkdirSync13, readdirSync as readdirSync10, readFileSync as readFileSync12, renameSync as renameSync8, writeFileSync as writeFileSync12 } from "fs";
|
|
5346
|
-
import { delimiter, join as
|
|
6179
|
+
import { delimiter, join as join19, resolve as resolve4 } from "path";
|
|
5347
6180
|
|
|
5348
6181
|
// src/daemon/workbuddyAppDb.ts
|
|
5349
|
-
import { execFile as
|
|
6182
|
+
import { execFile as execFile3 } from "child_process";
|
|
5350
6183
|
import { closeSync as closeSync6, existsSync as existsSync7, mkdirSync as mkdirSync12, openSync as openSync6, readdirSync as readdirSync9, readFileSync as readFileSync11, readSync as readSync5, renameSync as renameSync7, rmSync as rmSync8, statSync as statSync7, writeFileSync as writeFileSync11 } from "fs";
|
|
5351
6184
|
import { createRequire as createRequire2 } from "module";
|
|
5352
|
-
import { basename as
|
|
6185
|
+
import { basename as basename8, dirname as dirname5, isAbsolute as isAbsolute3, join as join18 } from "path";
|
|
5353
6186
|
|
|
5354
6187
|
// src/core/title.ts
|
|
5355
6188
|
import { closeSync as closeSync5, existsSync as existsSync6, openSync as openSync5, readFileSync as readFileSync10, readSync as readSync4, statSync as statSync6 } from "fs";
|
|
5356
6189
|
import { createRequire } from "module";
|
|
5357
|
-
import { homedir as
|
|
5358
|
-
import { basename as
|
|
6190
|
+
import { homedir as homedir5 } from "os";
|
|
6191
|
+
import { basename as basename7, dirname as dirname4, isAbsolute as isAbsolute2, join as join17 } from "path";
|
|
5359
6192
|
var TRANSCRIPT_TAIL_BYTES = 64 * 1024;
|
|
5360
6193
|
var SESSION_TITLE_MAX = 100;
|
|
5361
6194
|
function cleanTitle(text) {
|
|
@@ -5366,14 +6199,14 @@ function cleanTitle(text) {
|
|
|
5366
6199
|
var requireHere = createRequire(import.meta.url);
|
|
5367
6200
|
var QODER_DATA_DIRS = { qoder_cn: "com.qodercn.app.stable", qoder: "com.qoder.app.stable" };
|
|
5368
6201
|
var QODER_APP_DIR_ENV = { qoder_cn: "DESKBUDDY_QODER_CN_APP_DIR", qoder: "DESKBUDDY_QODER_APP_DIR" };
|
|
5369
|
-
function qoderDbPaths(tool, platform = process.platform, home =
|
|
6202
|
+
function qoderDbPaths(tool, platform = process.platform, home = homedir5(), env = process.env) {
|
|
5370
6203
|
let root;
|
|
5371
|
-
if (platform === "darwin") root =
|
|
5372
|
-
else if (platform === "win32") root = env.APPDATA ||
|
|
5373
|
-
else root = env.XDG_CONFIG_HOME ||
|
|
6204
|
+
if (platform === "darwin") root = join17(home, "Library", "Application Support");
|
|
6205
|
+
else if (platform === "win32") root = env.APPDATA || join17(home, "AppData", "Roaming");
|
|
6206
|
+
else root = env.XDG_CONFIG_HOME || join17(home, ".config");
|
|
5374
6207
|
const one = tool ? QODER_DATA_DIRS[tool] : void 0;
|
|
5375
6208
|
const tools = one ? [tool] : ["qoder_cn", "qoder"];
|
|
5376
|
-
return tools.map((t) =>
|
|
6209
|
+
return tools.map((t) => join17(env[QODER_APP_DIR_ENV[t] ?? ""] || join17(root, QODER_DATA_DIRS[t] ?? ""), "main.sqlite"));
|
|
5377
6210
|
}
|
|
5378
6211
|
|
|
5379
6212
|
// src/daemon/workbuddyAppDb.ts
|
|
@@ -5439,12 +6272,12 @@ function findWbTranscript(configDir, sessionId) {
|
|
|
5439
6272
|
if (!SESSION_UUID_RE.test(sessionId)) return null;
|
|
5440
6273
|
let dirs;
|
|
5441
6274
|
try {
|
|
5442
|
-
dirs = readdirSync9(
|
|
6275
|
+
dirs = readdirSync9(join18(configDir, "projects"));
|
|
5443
6276
|
} catch {
|
|
5444
6277
|
return null;
|
|
5445
6278
|
}
|
|
5446
6279
|
for (const d of dirs) {
|
|
5447
|
-
const p =
|
|
6280
|
+
const p = join18(configDir, "projects", d, `${sessionId}.jsonl`);
|
|
5448
6281
|
if (existsSync7(p)) return p;
|
|
5449
6282
|
}
|
|
5450
6283
|
return null;
|
|
@@ -5530,8 +6363,8 @@ function wbElectronCandidates(appCliPath) {
|
|
|
5530
6363
|
const m = /^(.*?)[\\/]resources[\\/]app\.asar\.unpacked[\\/]/i.exec(appCliPath);
|
|
5531
6364
|
if (!m) return [];
|
|
5532
6365
|
const root = m[1];
|
|
5533
|
-
if (/\.app[\\/]Contents$/.test(root)) return [
|
|
5534
|
-
return [
|
|
6366
|
+
if (/\.app[\\/]Contents$/.test(root)) return [join18(root, "MacOS", "Electron"), join18(root, "MacOS", "WorkBuddy")];
|
|
6367
|
+
return [join18(root, "WorkBuddy.exe")];
|
|
5535
6368
|
}
|
|
5536
6369
|
function defaultWbElectron(d = defaultLocateDeps()) {
|
|
5537
6370
|
if ((d.platform ?? process.platform) === "win32") return null;
|
|
@@ -5550,14 +6383,14 @@ function defaultAppRunning(platform = process.platform) {
|
|
|
5550
6383
|
return new Promise((resolve10) => {
|
|
5551
6384
|
try {
|
|
5552
6385
|
if (platform === "darwin") {
|
|
5553
|
-
|
|
6386
|
+
execFile3("pgrep", ["-f", "WorkBuddy.app/Contents/MacOS/"], { timeout: 5e3 }, (err) => {
|
|
5554
6387
|
const code = err?.code;
|
|
5555
6388
|
resolve10(!err ? true : code === 1 ? false : true);
|
|
5556
6389
|
});
|
|
5557
6390
|
return;
|
|
5558
6391
|
}
|
|
5559
6392
|
if (platform === "win32") {
|
|
5560
|
-
|
|
6393
|
+
execFile3(
|
|
5561
6394
|
"tasklist",
|
|
5562
6395
|
["/FI", "IMAGENAME eq WorkBuddy.exe", "/FO", "CSV", "/NH"],
|
|
5563
6396
|
{ timeout: 1e4, windowsHide: true, encoding: "utf8" },
|
|
@@ -5574,7 +6407,7 @@ function defaultAppRunning(platform = process.platform) {
|
|
|
5574
6407
|
var PENDING_MAX = 20;
|
|
5575
6408
|
var PENDING_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
5576
6409
|
function pendingPath() {
|
|
5577
|
-
return
|
|
6410
|
+
return join18(deskbuddyHome(), "workbuddy-pending.json");
|
|
5578
6411
|
}
|
|
5579
6412
|
function loadPendingFile() {
|
|
5580
6413
|
try {
|
|
@@ -5604,7 +6437,7 @@ var defaultWbAppDbDeps = () => ({
|
|
|
5604
6437
|
electron: () => defaultWbElectron(),
|
|
5605
6438
|
runElectron: (bin, script, input) => new Promise((resolve10, reject) => {
|
|
5606
6439
|
const env = { ...process.env, ELECTRON_RUN_AS_NODE: "1" };
|
|
5607
|
-
const c =
|
|
6440
|
+
const c = execFile3(
|
|
5608
6441
|
bin,
|
|
5609
6442
|
["-e", script],
|
|
5610
6443
|
{ env, timeout: 15e3, windowsHide: true, maxBuffer: 1024 * 1024 },
|
|
@@ -5702,7 +6535,7 @@ async function registerNow(job, d) {
|
|
|
5702
6535
|
const tp = findWbTranscript(d.configDir(), job.sessionId);
|
|
5703
6536
|
if (!tp) return "skip no_transcript";
|
|
5704
6537
|
const info = wbTranscriptInfo(tp);
|
|
5705
|
-
const title = info.title || cleanTitle(job.fallbackTitle ?? "") ||
|
|
6538
|
+
const title = info.title || cleanTitle(job.fallbackTitle ?? "") || basename8(job.cwd) || job.cwd;
|
|
5706
6539
|
const r = await runDb({ db, sessionId: job.sessionId, cwd: job.cwd, title, createdAt: info.createdAt, updatedAt: info.updatedAt }, d);
|
|
5707
6540
|
if (r === "no_sqlite") return "skip no_sqlite";
|
|
5708
6541
|
if (r.status === "schema") return `skip schema ${r.detail ?? ""}`.trim();
|
|
@@ -5744,7 +6577,7 @@ function loopbackWsUrl(wsUrl, port) {
|
|
|
5744
6577
|
async function connectCdp(port, d = {}) {
|
|
5745
6578
|
const fetchJson = d.fetchJson ?? defaultFetchJson;
|
|
5746
6579
|
const target = pickMainTarget(await fetchJson(`http://127.0.0.1:${port}/json/list`, 3e3));
|
|
5747
|
-
if (!target?.webSocketDebuggerUrl) throw new Error("\u627E\u4E0D\u5230
|
|
6580
|
+
if (!target?.webSocketDebuggerUrl) throw new Error("\u627E\u4E0D\u5230 App \u4E3B\u7A97\u53E3");
|
|
5748
6581
|
const url = loopbackWsUrl(target.webSocketDebuggerUrl, port);
|
|
5749
6582
|
if (!url) throw new Error("\u4E3B\u7A97\u53E3\u7684\u8C03\u8BD5\u5730\u5740\u4E0D\u5BF9");
|
|
5750
6583
|
const ws = (d.ws ?? ((u) => new WebSocket(u, { handshakeTimeout: 5e3 })))(url);
|
|
@@ -5842,7 +6675,7 @@ var VERIFY_MIN_MS = 30 * 1e3;
|
|
|
5842
6675
|
var VERIFY_MAX_MS = 60 * 1e3;
|
|
5843
6676
|
var VERIFY_STEP_MS = 5 * 1e3;
|
|
5844
6677
|
var defaultExec2 = (file3, args, timeoutMs = 1e4) => new Promise((done) => {
|
|
5845
|
-
|
|
6678
|
+
execFile4(file3, args, { timeout: timeoutMs, windowsHide: true, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }, (err, stdout6) => {
|
|
5846
6679
|
const code = err ? typeof err.code === "number" ? err.code : -1 : 0;
|
|
5847
6680
|
done({ code, stdout: String(stdout6 ?? "") });
|
|
5848
6681
|
});
|
|
@@ -5892,7 +6725,7 @@ function cleanLaunchEnv(src = process.env, shim = shimDir()) {
|
|
|
5892
6725
|
}
|
|
5893
6726
|
var REMEMBER_EXE_MS = 30 * 60 * 1e3;
|
|
5894
6727
|
var AWAY_IDLE_MS = 5 * 60 * 1e3;
|
|
5895
|
-
var keeperThrottleFile = () =>
|
|
6728
|
+
var keeperThrottleFile = () => join19(deskbuddyHome(), "wbapp", "keeper.json");
|
|
5896
6729
|
var filePersist = {
|
|
5897
6730
|
load: () => {
|
|
5898
6731
|
try {
|
|
@@ -5903,7 +6736,7 @@ var filePersist = {
|
|
|
5903
6736
|
},
|
|
5904
6737
|
save: (s2) => {
|
|
5905
6738
|
try {
|
|
5906
|
-
mkdirSync13(
|
|
6739
|
+
mkdirSync13(join19(deskbuddyHome(), "wbapp"), { recursive: true });
|
|
5907
6740
|
const f = keeperThrottleFile();
|
|
5908
6741
|
writeFileSync12(f + ".tmp", JSON.stringify(s2));
|
|
5909
6742
|
renameSync8(f + ".tmp", f);
|
|
@@ -5987,7 +6820,7 @@ function workbuddyBusy(now = Date.now(), dir3 = stateDir(), alive = isAlive) {
|
|
|
5987
6820
|
for (const f of readdirSync10(dir3)) {
|
|
5988
6821
|
if (!f.endsWith(".json")) continue;
|
|
5989
6822
|
try {
|
|
5990
|
-
const s2 = JSON.parse(readFileSync12(
|
|
6823
|
+
const s2 = JSON.parse(readFileSync12(join19(dir3, f), "utf8"));
|
|
5991
6824
|
if (s2.tool !== "workbuddy" || s2.state !== "watch" && s2.state !== "ask_approval") continue;
|
|
5992
6825
|
if (typeof s2.pid === "number" && s2.pid > 0) {
|
|
5993
6826
|
if (alive(s2.pid, s2.pidName)) return true;
|
|
@@ -6610,7 +7443,7 @@ function choicesToSet(choices) {
|
|
|
6610
7443
|
options.push({ id, label: c.label, kind, ...c.description ? { description: c.description } : {} });
|
|
6611
7444
|
actions[id] = { decision: kind === "deny" ? "deny" : "allow", native: c.native ?? { reject: true } };
|
|
6612
7445
|
});
|
|
6613
|
-
return checkOptions({ options, actions }, { anyKinds: true
|
|
7446
|
+
return checkOptions({ options, actions }, { anyKinds: true });
|
|
6614
7447
|
}
|
|
6615
7448
|
function asNative(v) {
|
|
6616
7449
|
if (!v || typeof v !== "object") return null;
|
|
@@ -6642,7 +7475,7 @@ var WB_OWN_TEXT = {
|
|
|
6642
7475
|
var str3 = (v) => typeof v === "string" ? v.trim() : "";
|
|
6643
7476
|
function noticeCard(title, lang, why) {
|
|
6644
7477
|
const O = WB_OWN_TEXT[lang];
|
|
6645
|
-
const set = checkOptions({ options: [{ id: "o0", label: O.noticeButton, kind: "deny" }], actions: { o0: { decision: "deny" } } }, { anyKinds: true
|
|
7478
|
+
const set = checkOptions({ options: [{ id: "o0", label: O.noticeButton, kind: "deny" }], actions: { o0: { decision: "deny" } } }, { anyKinds: true });
|
|
6646
7479
|
return { type: "notice", options: set, summary: `${O.noticePrefix}${title || O.pending}`, why };
|
|
6647
7480
|
}
|
|
6648
7481
|
function rawChoices(p, lang) {
|
|
@@ -6749,7 +7582,7 @@ function wbCard(p, lang) {
|
|
|
6749
7582
|
}
|
|
6750
7583
|
function fallbackCard(p, lang) {
|
|
6751
7584
|
const O = WB_OWN_TEXT[lang];
|
|
6752
|
-
const set = checkOptions({ options: [{ id: "o0", label: O.reject, kind: "deny" }], actions: { o0: { decision: "deny", native: { reject: true } } } }, { anyKinds: true
|
|
7585
|
+
const set = checkOptions({ options: [{ id: "o0", label: O.reject, kind: "deny" }], actions: { o0: { decision: "deny", native: { reject: true } } } }, { anyKinds: true });
|
|
6753
7586
|
const tool = str3(p.toolName);
|
|
6754
7587
|
return { type: "options", options: set, toolName: tool || "WorkBuddy", toolInput: {}, summary: tool ? `${tool} ${O.pending}` : O.pending };
|
|
6755
7588
|
}
|
|
@@ -6990,7 +7823,7 @@ function domOptions(labels, lang) {
|
|
|
6990
7823
|
const first = options.find((o) => o.kind !== "deny");
|
|
6991
7824
|
if (first) first.kind = "allow";
|
|
6992
7825
|
}
|
|
6993
|
-
return checkOptions({ options, actions }, { anyKinds: true
|
|
7826
|
+
return checkOptions({ options, actions }, { anyKinds: true });
|
|
6994
7827
|
}
|
|
6995
7828
|
var WorkbuddyAppWatch = class {
|
|
6996
7829
|
#d;
|
|
@@ -7245,25 +8078,25 @@ var WorkbuddyAppWatch = class {
|
|
|
7245
8078
|
}
|
|
7246
8079
|
}
|
|
7247
8080
|
async #sdkRound() {
|
|
7248
|
-
const
|
|
7249
|
-
if (!
|
|
7250
|
-
this.#snapshotBusy =
|
|
8081
|
+
const snap2 = await this.#eval(SNAPSHOT);
|
|
8082
|
+
if (!snap2 || !Array.isArray(snap2.sessions)) return;
|
|
8083
|
+
this.#snapshotBusy = snap2.busy === true;
|
|
7251
8084
|
this.#snapshotBusyAt = this.#now();
|
|
7252
8085
|
const present = /* @__PURE__ */ new Set();
|
|
7253
8086
|
const current = /* @__PURE__ */ new Map();
|
|
7254
|
-
for (const s2 of
|
|
8087
|
+
for (const s2 of snap2.sessions) {
|
|
7255
8088
|
if (!s2?.id) continue;
|
|
7256
8089
|
present.add(s2.id);
|
|
7257
8090
|
if (s2.pending?.id) current.set(s2.id, s2.pending.id);
|
|
7258
8091
|
}
|
|
7259
8092
|
this.#withdrawStale(present, current);
|
|
7260
8093
|
this.#prune();
|
|
7261
|
-
const lang = wbLang(
|
|
8094
|
+
const lang = wbLang(snap2.locale);
|
|
7262
8095
|
for (const sid of [...this.#missingSince.keys()]) {
|
|
7263
|
-
const sp =
|
|
8096
|
+
const sp = snap2.sessions.find((x) => x?.id === sid)?.pending;
|
|
7264
8097
|
if (!sp || sp.category !== "pendingMissing") this.#missingSince.delete(sid);
|
|
7265
8098
|
}
|
|
7266
|
-
for (const s2 of
|
|
8099
|
+
for (const s2 of snap2.sessions) {
|
|
7267
8100
|
const p = s2?.pending;
|
|
7268
8101
|
if (!s2?.id || !p?.id) continue;
|
|
7269
8102
|
const key = `${s2.id}:${p.id}`;
|
|
@@ -7273,7 +8106,7 @@ var WorkbuddyAppWatch = class {
|
|
|
7273
8106
|
this.#missingSince.set(s2.id, since);
|
|
7274
8107
|
if (this.#now() - since < MISSING_STABLE_MS) continue;
|
|
7275
8108
|
}
|
|
7276
|
-
if (!lang) this.#skipLog(`lang:${
|
|
8109
|
+
if (!lang) this.#skipLog(`lang:${snap2.locale}`, `WorkBuddy \u754C\u9762\u8BED\u8A00 ${snap2.locale || "(\u672A\u77E5)"} \u6CA1\u6709\u539F\u6587\u8868\uFF0C\u5F85\u786E\u8BA4\u53EA\u63A8\u63D0\u793A\u5361`);
|
|
7277
8110
|
const c = wbCard(p, lang);
|
|
7278
8111
|
if (c.type === "notice") {
|
|
7279
8112
|
this.#skipLog(key, `WorkBuddy \u4F1A\u8BDD ${s2.id.slice(0, 8)} \u7684\u5F85\u786E\u8BA4\uFF08${c.why}\uFF09\u5728\u8BBE\u5907\u4E0A\u53EA\u63A8\u63D0\u793A\u5361`);
|
|
@@ -7286,11 +8119,11 @@ var WorkbuddyAppWatch = class {
|
|
|
7286
8119
|
}
|
|
7287
8120
|
}
|
|
7288
8121
|
async #domRound() {
|
|
7289
|
-
const
|
|
7290
|
-
if (!
|
|
7291
|
-
const sid =
|
|
7292
|
-
const labels = Array.isArray(
|
|
7293
|
-
const rawText = typeof
|
|
8122
|
+
const snap2 = await this.#eval(DOM_SNAPSHOT);
|
|
8123
|
+
if (!snap2) return;
|
|
8124
|
+
const sid = snap2.sessionId ?? "";
|
|
8125
|
+
const labels = Array.isArray(snap2.card?.labels) ? snap2.card.labels.filter((l) => typeof l === "string") : null;
|
|
8126
|
+
const rawText = typeof snap2.card?.text === "string" ? snap2.card.text.slice(0, 2e3) : "";
|
|
7294
8127
|
const textNorm = normalizeDomText(rawText);
|
|
7295
8128
|
const fp = labels ? `dom:${labels.join("|")}::${textNorm}` : "";
|
|
7296
8129
|
const present = /* @__PURE__ */ new Set();
|
|
@@ -7310,9 +8143,9 @@ var WorkbuddyAppWatch = class {
|
|
|
7310
8143
|
else this.#domFp.delete(sid);
|
|
7311
8144
|
}
|
|
7312
8145
|
if (!sid || !labels) return;
|
|
7313
|
-
const lang = wbLang(
|
|
8146
|
+
const lang = wbLang(snap2.locale);
|
|
7314
8147
|
if (!lang) {
|
|
7315
|
-
this.#skipLog(`lang:${
|
|
8148
|
+
this.#skipLog(`lang:${snap2.locale}`, `WorkBuddy \u754C\u9762\u8BED\u8A00 ${snap2.locale || "(\u672A\u77E5)"} \u6CA1\u6709\u539F\u6587\u8868\uFF0C\u5F85\u786E\u8BA4\u4E0D\u63A8\u8BBE\u5907`);
|
|
7316
8149
|
return;
|
|
7317
8150
|
}
|
|
7318
8151
|
const key = `${sid}:${fp}`;
|
|
@@ -7463,8 +8296,8 @@ function isQoderDesktopPid(pidName) {
|
|
|
7463
8296
|
|
|
7464
8297
|
// src/daemon/qoderApp/keeper.ts
|
|
7465
8298
|
import { existsSync as existsSync9, mkdirSync as mkdirSync14, readdirSync as readdirSync11, readFileSync as readFileSync13, renameSync as renameSync9, writeFileSync as writeFileSync13 } from "fs";
|
|
7466
|
-
import { homedir as
|
|
7467
|
-
import { join as
|
|
8299
|
+
import { homedir as homedir6 } from "os";
|
|
8300
|
+
import { join as join20 } from "path";
|
|
7468
8301
|
var WAKE_LAUNCH_GAP_MS2 = 60 * 1e3;
|
|
7469
8302
|
var RESTART_GAP_MS2 = 30 * 60 * 1e3;
|
|
7470
8303
|
var RESTART_DAILY_MAX2 = 3;
|
|
@@ -7503,7 +8336,7 @@ function toolBusy(tool, now = Date.now(), dir3 = stateDir(), alive = isAlive) {
|
|
|
7503
8336
|
for (const f of readdirSync11(dir3)) {
|
|
7504
8337
|
if (!f.endsWith(".json")) continue;
|
|
7505
8338
|
try {
|
|
7506
|
-
const s2 = JSON.parse(readFileSync13(
|
|
8339
|
+
const s2 = JSON.parse(readFileSync13(join20(dir3, f), "utf8"));
|
|
7507
8340
|
if (s2.tool !== tool || s2.state !== "watch" && s2.state !== "ask_approval") continue;
|
|
7508
8341
|
if (typeof s2.pid === "number" && s2.pid > 0) {
|
|
7509
8342
|
if (alive(s2.pid, s2.pidName)) return true;
|
|
@@ -7516,7 +8349,7 @@ function toolBusy(tool, now = Date.now(), dir3 = stateDir(), alive = isAlive) {
|
|
|
7516
8349
|
return false;
|
|
7517
8350
|
}
|
|
7518
8351
|
function defaultThrottleFile() {
|
|
7519
|
-
return
|
|
8352
|
+
return join20(deskbuddyHome(), "qoderapp", "keeper.json");
|
|
7520
8353
|
}
|
|
7521
8354
|
var QoderCdpKeeper = class {
|
|
7522
8355
|
#d;
|
|
@@ -7563,7 +8396,7 @@ var QoderCdpKeeper = class {
|
|
|
7563
8396
|
all = {};
|
|
7564
8397
|
}
|
|
7565
8398
|
all[this.#d.brand.tool] = t;
|
|
7566
|
-
mkdirSync14(
|
|
8399
|
+
mkdirSync14(join20(f, ".."), { recursive: true });
|
|
7567
8400
|
writeFileSync13(f + ".tmp", JSON.stringify(all));
|
|
7568
8401
|
renameSync9(f + ".tmp", f);
|
|
7569
8402
|
} catch {
|
|
@@ -7601,7 +8434,7 @@ var QoderCdpKeeper = class {
|
|
|
7601
8434
|
if (this.#d.appPath) return this.#d.appPath();
|
|
7602
8435
|
if (this.#platform() !== "darwin") return null;
|
|
7603
8436
|
const name = this.#d.brand.macApp.split("/").pop() ?? "";
|
|
7604
|
-
for (const p of [this.#d.brand.macApp,
|
|
8437
|
+
for (const p of [this.#d.brand.macApp, join20(homedir6(), "Applications", name)]) if (existsSync9(p)) return p;
|
|
7605
8438
|
return null;
|
|
7606
8439
|
}
|
|
7607
8440
|
/** 一轮:健康检查 → 活着核对属主。🔴 平时不重启 App */
|
|
@@ -7710,7 +8543,7 @@ var PROBE2 = `(() => {
|
|
|
7710
8543
|
return { ok: missing.length === 0, missing };
|
|
7711
8544
|
})()`;
|
|
7712
8545
|
function sendExpr(i) {
|
|
7713
|
-
const payload = { sessionId: i.sessionId, agentId: "agent:default", inputId: i.inputId, prompt: i.prompt };
|
|
8546
|
+
const payload = { sessionId: i.sessionId, agentId: "agent:default", inputId: i.inputId, prompt: i.prompt, permissionMode: "default" };
|
|
7714
8547
|
if (i.isNew) {
|
|
7715
8548
|
payload.turnId = i.inputId;
|
|
7716
8549
|
payload.executionKind = "local";
|
|
@@ -7837,12 +8670,13 @@ var QoderAppConn = class {
|
|
|
7837
8670
|
this.#keeperAt = 0;
|
|
7838
8671
|
this.#retryAt = 0;
|
|
7839
8672
|
}
|
|
7840
|
-
|
|
8673
|
+
/** timeoutMs:设备授权卡查选项要快,单独给短超时(缺省 EVAL_TIMEOUT_MS) */
|
|
8674
|
+
async eval(expr, timeoutMs = EVAL_TIMEOUT_MS) {
|
|
7841
8675
|
if (!this.available()) return null;
|
|
7842
8676
|
const c = this.#client;
|
|
7843
8677
|
if (!c) return null;
|
|
7844
8678
|
try {
|
|
7845
|
-
return await c.evaluate(expr,
|
|
8679
|
+
return await c.evaluate(expr, timeoutMs);
|
|
7846
8680
|
} catch (e) {
|
|
7847
8681
|
this.#d.log(`${this.#d.brand.label} App \u4EFB\u52A1\u811A\u672C\u51FA\u9519\uFF1A${e?.message ?? e}`);
|
|
7848
8682
|
return null;
|
|
@@ -7941,6 +8775,224 @@ var QoderAppConn = class {
|
|
|
7941
8775
|
}
|
|
7942
8776
|
};
|
|
7943
8777
|
|
|
8778
|
+
// src/daemon/qoderApp/approval.ts
|
|
8779
|
+
var APP_LOOKUP_TIMEOUT_MS = 3e3;
|
|
8780
|
+
var APP_LOOKUP_RETRY_MS = [250, 500, 1e3];
|
|
8781
|
+
function pendingCardExpr(q) {
|
|
8782
|
+
const want = { sessionId: q.sessionId, toolName: q.toolName, toolUseId: q.toolUseId ?? "", input: q.toolUseId ? null : q.toolInput ?? null };
|
|
8783
|
+
return `(async () => {
|
|
8784
|
+
const q = globalThis.qoderDesktop;
|
|
8785
|
+
if (!q || typeof q.watchChatSessionControl !== 'function' || typeof q.unwatchChatSessionControl !== 'function') return { ok: false, why: 'nobridge' };
|
|
8786
|
+
const NR = new RegExp(${JSON.stringify(NOT_REGISTERED_RE_SRC)}, 'i');
|
|
8787
|
+
const msg = (e) => String((e && e.message) || e).slice(0, 300);
|
|
8788
|
+
const want = ${JSON.stringify(want)};
|
|
8789
|
+
const wid = 'deskbuddy:perm:' + Date.now().toString(36) + ':' + Math.random().toString(36).slice(2);
|
|
8790
|
+
let st = null;
|
|
8791
|
+
try { const r = await q.watchChatSessionControl(want.sessionId, wid); st = r && r.state; }
|
|
8792
|
+
catch (e) { return { ok: false, why: NR.test(msg(e)) ? 'gone' : 'error', message: msg(e) }; }
|
|
8793
|
+
finally { try { await q.unwatchChatSessionControl(want.sessionId, wid); } catch (e) {} }
|
|
8794
|
+
if (!st || typeof st !== 'object') return { ok: false, why: 'error', message: 'no state' };
|
|
8795
|
+
const busy = st.runtimeState === 'running' || st.runtimeState === 'opening' || st.runtimeState === 'waiting-user' || !!st.activeTurn;
|
|
8796
|
+
const list = Array.isArray(st.pendingInteractions) ? st.pendingInteractions.filter((p) => p && p.type === 'permission') : [];
|
|
8797
|
+
const canon = (v) => JSON.stringify(v, (k, x) => (x && typeof x === 'object' && !Array.isArray(x) ? Object.keys(x).sort().reduce((o, k2) => ((o[k2] = x[k2]), o), {}) : x));
|
|
8798
|
+
const hits = want.toolUseId ? list.filter((p) => p.toolUseId === want.toolUseId) : list.filter((p) => p.toolName === want.toolName && canon(p.input) === canon(want.input));
|
|
8799
|
+
if (hits.length === 0) return { ok: false, why: 'not_found', busy };
|
|
8800
|
+
// \u6309\u53C2\u6570\u5339\u914D\uFF1AApp \u91CC\u540C\u540D\u540C\u53C2\u7684\u5F85\u786E\u8BA4\u4E0D\u6B62\u4E00\u6761\uFF08\u4E0D\u7BA1\u662F\u5426\u5DF2\u88AB\u522B\u7684\u8BBE\u5907\u5361\u5BF9\u4E0A\uFF09\u5C31\u5206\u4E0D\u6E05\u662F\u54EA\u4E00\u6B21\u8C03\u7528 \u2192 \u9000\u56DE\u67E5\u8868
|
|
8801
|
+
if (hits.length > 1) return { ok: false, why: 'ambiguous' };
|
|
8802
|
+
const p = hits[0];
|
|
8803
|
+
if (p.toolName !== want.toolName) return { ok: false, why: 'mismatch' };
|
|
8804
|
+
const isI18n = (v) => v && typeof v === 'object' && typeof v.getFixedT === 'function' && typeof v.t === 'function' && typeof v.exists === 'function';
|
|
8805
|
+
const findI18n = () => {
|
|
8806
|
+
const roots = [document.getElementById('root'), ...Array.from(document.body ? document.body.children : [])];
|
|
8807
|
+
for (const el of roots) {
|
|
8808
|
+
if (!el) continue;
|
|
8809
|
+
const ck = Object.keys(el).find((k) => k.startsWith('__reactContainer$'));
|
|
8810
|
+
if (!ck) continue;
|
|
8811
|
+
const stack = [el[ck]];
|
|
8812
|
+
let n = 0;
|
|
8813
|
+
while (stack.length && n < 50000) {
|
|
8814
|
+
const f = stack.pop();
|
|
8815
|
+
if (!f) continue;
|
|
8816
|
+
n++;
|
|
8817
|
+
let h = f.memoizedState, m = 0;
|
|
8818
|
+
while (h && typeof h === 'object' && m < 80) {
|
|
8819
|
+
const ms = h.memoizedState;
|
|
8820
|
+
const c = [ms, Array.isArray(ms) ? ms[0] : null, Array.isArray(ms) && ms[0] ? ms[0].i18n : null];
|
|
8821
|
+
for (const v of c) { const real = v && typeof v === 'object' && isI18n(v.__original) ? v.__original : v; if (isI18n(real)) return real; }
|
|
8822
|
+
h = h.next;
|
|
8823
|
+
m++;
|
|
8824
|
+
}
|
|
8825
|
+
if (f.sibling) stack.push(f.sibling);
|
|
8826
|
+
if (f.child) stack.push(f.child);
|
|
8827
|
+
}
|
|
8828
|
+
}
|
|
8829
|
+
return null;
|
|
8830
|
+
};
|
|
8831
|
+
const input = p.input && typeof p.input === 'object' && !Array.isArray(p.input) ? p.input : {};
|
|
8832
|
+
const mode = typeof input.mode === 'string' ? input.mode : '';
|
|
8833
|
+
if ((p.toolName === 'CollaborationAuthorizeProjectAccess' && mode === 'collaboration_access_authorization') || (p.toolName === 'LarkAuthorize' && mode === 'lark_authorization') || p.toolName === 'EnterPlanMode' || p.toolName === 'ExitPlanMode')
|
|
8834
|
+
return { ok: false, why: 'unsupported', message: p.toolName };
|
|
8835
|
+
const i18n = findI18n();
|
|
8836
|
+
if (!i18n) return { ok: false, why: 'no_i18n' };
|
|
8837
|
+
const T = (k, o) => { if (!i18n.exists(k)) throw new Error('missing ' + k); const s = i18n.t(k, o); if (typeof s !== 'string' || !s) throw new Error('empty ' + k); return s; };
|
|
8838
|
+
const modeLabel = (m) => (m === 'default' ? T('composer.permission.acceptEdits.label') : m === 'acceptEdits' || m === 'auto' || m === 'bypassPermissions' ? T('composer.permission.' + m + '.label') : m);
|
|
8839
|
+
const ruleScope = (r) => ((r.ruleContent && String(r.ruleContent).endsWith(':*') ? String(r.ruleContent).slice(0, -2) : r.ruleContent) || '').trim() || r.toolName;
|
|
8840
|
+
const suggLabel = (s) => s.type === 'mode' ? T('permission.suggestion.mode', { mode: modeLabel(s.mode) })
|
|
8841
|
+
: s.type === 'directories' ? T('permission.suggestion.directories.' + s.operation, { scope: s.directories.join(', ') })
|
|
8842
|
+
: s.operation === 'add' ? T('permission.suggestion.rules.add.' + s.behavior, { scope: s.rules.map(ruleScope).join(', ') })
|
|
8843
|
+
: T('permission.suggestion.rules.' + s.operation, { behavior: T('permission.suggestion.behavior.' + s.behavior), scope: s.rules.map(ruleScope).join(', ') });
|
|
8844
|
+
const items = [];
|
|
8845
|
+
let variant = 'default';
|
|
8846
|
+
try {
|
|
8847
|
+
if (p.toolName === 'dws_bash' && mode === 'pat_authorization') {
|
|
8848
|
+
variant = 'pat';
|
|
8849
|
+
const g = Array.isArray(input.grantOptions) ? input.grantOptions.filter((x) => typeof x === 'string') : [];
|
|
8850
|
+
items.push({ key: 'deny', label: T('permission.deny') });
|
|
8851
|
+
if (!(g.length > 0) || g.includes('once')) items.push({ key: 'patOnce', label: T('permission.pat.allowOnce') });
|
|
8852
|
+
if (g.includes('session')) items.push({ key: 'patSession', label: T('permission.pat.allowSession') });
|
|
8853
|
+
if (g.includes('permanent')) items.push({ key: 'patAlways', label: T('permission.pat.allowAlways') });
|
|
8854
|
+
} else if (p.toolName === 'RequestDirectory' && mode === 'directory_connection') {
|
|
8855
|
+
variant = 'directory';
|
|
8856
|
+
items.push({ key: 'deny', label: T('permission.deny') }, { key: 'allowOnce', label: T('permission.allow') });
|
|
8857
|
+
} else if (p.reason && p.reason.kind === 'repeatedToolCall') {
|
|
8858
|
+
// \u300C\u7EC8\u6B62\u4EFB\u52A1\u300D\u5728\u6E32\u67D3\u5C42\u5C31\u662F onClick=V1 \u2192 respondPermission(deny)\uFF0C\u53EA\u62D2\u8FD9\u4E00\u6B21\u8C03\u7528\uFF08\u4E0E hook deny \u540C\u4E49\uFF09\uFF0C\u5E76\u4E0D\u4E2D\u65AD\u4F1A\u8BDD
|
|
8859
|
+
variant = 'repeated';
|
|
8860
|
+
items.push({ key: 'deny', label: T('permission.repeatedTool.stopTask') }, { key: 'allowOnce', label: T('permission.repeatedTool.allowOnce') });
|
|
8861
|
+
} else {
|
|
8862
|
+
items.push({ key: 'deny', label: T('permission.deny') }, { key: 'allowOnce', label: T('permission.allowOnce') });
|
|
8863
|
+
for (const s of Array.isArray(p.permissionSuggestions) ? p.permissionSuggestions : []) {
|
|
8864
|
+
if (!s || typeof s.id !== 'string') return { ok: false, why: 'bad_suggestion' };
|
|
8865
|
+
items.push({ key: 'suggestion', label: suggLabel(s), suggestion: s });
|
|
8866
|
+
}
|
|
8867
|
+
}
|
|
8868
|
+
} catch (e) {
|
|
8869
|
+
return { ok: false, why: 'no_i18n', message: msg(e) };
|
|
8870
|
+
}
|
|
8871
|
+
return { ok: true, toolUseId: p.toolUseId, toolName: p.toolName, matchedBy: want.toolUseId ? 'id' : 'input', variant, lang: String(i18n.resolvedLanguage || i18n.language || ''), items };
|
|
8872
|
+
})()`;
|
|
8873
|
+
}
|
|
8874
|
+
function respondSuggestionExpr(sessionId, toolUseId, permissionSuggestionId) {
|
|
8875
|
+
const payload = { type: "permission", sessionId, toolUseId, decision: "allow", permissionSuggestionId };
|
|
8876
|
+
return `(async () => {
|
|
8877
|
+
const q = globalThis.qoderDesktop;
|
|
8878
|
+
if (!q || typeof q.respondChatInteraction !== 'function') return { ok: false, why: 'nobridge' };
|
|
8879
|
+
const msg = (e) => String((e && e.message) || e).slice(0, 300);
|
|
8880
|
+
try { await q.respondChatInteraction(${JSON.stringify(payload)}); return { ok: true }; }
|
|
8881
|
+
catch (e) { const m = msg(e); return { ok: false, why: /\u786E\u8BA4\u8BF7\u6C42\u5DF2\u7ECF\u5931\u6548|not pending|no longer pending/i.test(m) ? 'gone' : 'error', message: m }; }
|
|
8882
|
+
})()`;
|
|
8883
|
+
}
|
|
8884
|
+
var RULE_BEHAVIOR_ZH2 = { allow: "\u5141\u8BB8", deny: "\u62D2\u7EDD", ask: "\u8BE2\u95EE" };
|
|
8885
|
+
function sameJson(a, b) {
|
|
8886
|
+
const canon = (v) => JSON.stringify(
|
|
8887
|
+
v,
|
|
8888
|
+
(_k, x) => x && typeof x === "object" && !Array.isArray(x) ? Object.keys(x).sort().reduce((o, k) => (o[k] = x[k], o), {}) : x
|
|
8889
|
+
);
|
|
8890
|
+
return canon(a) === canon(b);
|
|
8891
|
+
}
|
|
8892
|
+
function suggestionUpdate(s2, hookSuggestions) {
|
|
8893
|
+
const m = /^session-(\d+)$/.exec(s2.id);
|
|
8894
|
+
const orig = m && Array.isArray(hookSuggestions) ? hookSuggestions[Number(m[1])] : void 0;
|
|
8895
|
+
const rebuilt = s2.type === "rules" ? { type: s2.operation === "add" ? "addRules" : s2.operation === "replace" ? "replaceRules" : "removeRules", behavior: s2.behavior, rules: s2.rules, destination: "session" } : s2.type === "mode" ? { type: "setMode", mode: s2.mode, destination: "session" } : { type: s2.operation === "add" ? "addDirectories" : "removeDirectories", directories: s2.directories, destination: "session" };
|
|
8896
|
+
if (orig && typeof orig === "object" && orig.destination === "session" && orig.type === rebuilt.type) {
|
|
8897
|
+
const same = s2.type === "rules" ? orig.behavior === s2.behavior && sameJson(orig.rules, s2.rules) : s2.type === "mode" ? orig.mode === s2.mode : sameJson(orig.directories, s2.directories);
|
|
8898
|
+
if (same) return orig;
|
|
8899
|
+
}
|
|
8900
|
+
return rebuilt;
|
|
8901
|
+
}
|
|
8902
|
+
function ruleDesc(s2) {
|
|
8903
|
+
if (s2.type !== "rules" || !Array.isArray(s2.rules) || s2.rules.length === 0) return "";
|
|
8904
|
+
const op = s2.operation === "add" ? "" : s2.operation === "replace" ? "\u66FF\u6362\u4E3A" : "\u79FB\u9664";
|
|
8905
|
+
const scope = s2.rules.map((r) => typeof r.ruleContent === "string" && r.ruleContent ? `${r.toolName}(${r.ruleContent})` : `\u6574\u4E2A ${r.toolName} \u5DE5\u5177`).join("\u3001");
|
|
8906
|
+
const kindText = s2.operation === "add" && s2.behavior === "allow" ? "" : `${RULE_BEHAVIOR_ZH2[s2.behavior] ?? s2.behavior}`;
|
|
8907
|
+
return `${kindText}\u89C4\u5219${op}\uFF1A${scope} \u2192 session`;
|
|
8908
|
+
}
|
|
8909
|
+
function appCardOptions(card, hook) {
|
|
8910
|
+
const options = [];
|
|
8911
|
+
const actions = {};
|
|
8912
|
+
const input = hook.tool_input && typeof hook.tool_input === "object" && !Array.isArray(hook.tool_input) ? hook.tool_input : {};
|
|
8913
|
+
let n = 0;
|
|
8914
|
+
for (const it of card.items) {
|
|
8915
|
+
if (!it || typeof it.label !== "string") return null;
|
|
8916
|
+
if (it.key === "deny") {
|
|
8917
|
+
options.push({ id: "deny", label: it.label, kind: "deny" });
|
|
8918
|
+
actions.deny = { decision: "deny" };
|
|
8919
|
+
} else if (it.key === "allowOnce") {
|
|
8920
|
+
options.push({ id: "allow", label: it.label, kind: "allow" });
|
|
8921
|
+
actions.allow = { decision: "allow" };
|
|
8922
|
+
} else if (it.key === "patOnce" || it.key === "patSession" || it.key === "patAlways") {
|
|
8923
|
+
const [id, kind, grantType] = it.key === "patOnce" ? ["allow", "allow", "once"] : it.key === "patSession" ? ["session", "allow_session", "session"] : ["always", "allow_always", "permanent"];
|
|
8924
|
+
options.push({ id, label: it.label, kind });
|
|
8925
|
+
actions[id] = { decision: "allow", updatedInput: { ...input, grantType } };
|
|
8926
|
+
} else if (it.key === "suggestion" && it.suggestion) {
|
|
8927
|
+
const s2 = it.suggestion;
|
|
8928
|
+
const id = `s${n++}`;
|
|
8929
|
+
const desc = ruleDesc(s2);
|
|
8930
|
+
options.push({ id, label: it.label, ...desc ? { description: desc } : {}, kind: "allow_session" });
|
|
8931
|
+
actions[id] = {
|
|
8932
|
+
decision: "allow",
|
|
8933
|
+
updatedPermissions: [suggestionUpdate(s2, hook.permission_suggestions)],
|
|
8934
|
+
// 🔴 只有按 tool_use_id 精确对上的卡才经 App 回答:按参数匹配到的可能是同名同参的另一次调用,答错对象
|
|
8935
|
+
...s2.type === "mode" && card.matchedBy === "id" ? { qoderApp: { toolUseId: card.toolUseId, permissionSuggestionId: s2.id } } : {}
|
|
8936
|
+
};
|
|
8937
|
+
} else return null;
|
|
8938
|
+
}
|
|
8939
|
+
return checkOptions({ options, actions });
|
|
8940
|
+
}
|
|
8941
|
+
var QoderAppApprovals = class {
|
|
8942
|
+
#d;
|
|
8943
|
+
constructor(deps2) {
|
|
8944
|
+
this.#d = deps2;
|
|
8945
|
+
}
|
|
8946
|
+
#conn(tool) {
|
|
8947
|
+
if (tool !== "qoder_cn" && tool !== "qoder") return null;
|
|
8948
|
+
try {
|
|
8949
|
+
const c = this.#d.conn(tool);
|
|
8950
|
+
return c && c.available() ? c : null;
|
|
8951
|
+
} catch {
|
|
8952
|
+
return null;
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
/**
|
|
8956
|
+
* 这条 hook 请求在 App 里对应的确认卡 → 设备选项。拿不到返回 null(调用方退回查表)。
|
|
8957
|
+
* 会话在 App 里跑着、但还没看到这一条时短暂重看(App 收 worker 的确认请求与 hook 进程起来几乎同时)
|
|
8958
|
+
*/
|
|
8959
|
+
async options(tool, hook, signal) {
|
|
8960
|
+
const conn = this.#conn(tool);
|
|
8961
|
+
if (!conn) return null;
|
|
8962
|
+
const sessionId = typeof hook.session_id === "string" ? hook.session_id : "";
|
|
8963
|
+
const toolName = typeof hook.tool_name === "string" ? hook.tool_name : "";
|
|
8964
|
+
if (!sessionId || !toolName) return null;
|
|
8965
|
+
const toolUseId = typeof hook.tool_use_id === "string" && hook.tool_use_id ? hook.tool_use_id : void 0;
|
|
8966
|
+
const expr = pendingCardExpr({ sessionId, toolName, toolUseId, toolInput: hook.tool_input });
|
|
8967
|
+
const sleep3 = this.#d.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
8968
|
+
let last = "";
|
|
8969
|
+
for (let i = 0; ; i++) {
|
|
8970
|
+
if (signal?.aborted) return null;
|
|
8971
|
+
const r = await conn.eval(expr, APP_LOOKUP_TIMEOUT_MS);
|
|
8972
|
+
if (r && r.ok === true) {
|
|
8973
|
+
const set = appCardOptions(r, hook);
|
|
8974
|
+
if (!set) this.#d.log(`${tool} App \u6388\u6743\u5361\u9009\u9879\u81EA\u68C0\u4E0D\u8FC7\uFF08${r.variant}\uFF09\uFF0C\u9000\u56DE\u67E5\u8868`);
|
|
8975
|
+
return set;
|
|
8976
|
+
}
|
|
8977
|
+
last = r ? `${r.why}${r.message ? `:${r.message}` : ""}` : "timeout";
|
|
8978
|
+
if (!r || r.why !== "not_found" || !r.busy || i >= APP_LOOKUP_RETRY_MS.length) break;
|
|
8979
|
+
await sleep3(APP_LOOKUP_RETRY_MS[i]);
|
|
8980
|
+
}
|
|
8981
|
+
this.#d.log(`${tool} App \u6388\u6743\u5361\u6CA1\u53D6\u5230\u771F\u5B9E\u9009\u9879\uFF08${last}\uFF09\uFF0C\u9000\u56DE\u67E5\u8868`);
|
|
8982
|
+
return null;
|
|
8983
|
+
}
|
|
8984
|
+
/** 设备选了「本次任务使用 X 模式」:经 App 回答。ok = App 接了;gone = 这条已不在待确认(电脑上已处理);failed = 没回成 */
|
|
8985
|
+
async respond(tool, sessionId, toolUseId, permissionSuggestionId) {
|
|
8986
|
+
const conn = this.#conn(tool);
|
|
8987
|
+
if (!conn) return "failed";
|
|
8988
|
+
const r = await conn.eval(respondSuggestionExpr(sessionId, toolUseId, permissionSuggestionId), APP_LOOKUP_TIMEOUT_MS);
|
|
8989
|
+
if (r?.ok === true) return "ok";
|
|
8990
|
+
if (r?.why === "gone") return "gone";
|
|
8991
|
+
this.#d.log(`${tool} App \u56DE\u7B54\u6388\u6743\u5931\u8D25\uFF08${r ? `${r.why}${r.message ? `:${r.message}` : ""}` : "timeout"}\uFF09\uFF0C\u6539\u7531 hook \u56DE\u7B54`);
|
|
8992
|
+
return "failed";
|
|
8993
|
+
}
|
|
8994
|
+
};
|
|
8995
|
+
|
|
7944
8996
|
// src/daemon/qoderApp/task.ts
|
|
7945
8997
|
import { createHash as createHash3 } from "crypto";
|
|
7946
8998
|
import { realpathSync as realpathSync4 } from "fs";
|
|
@@ -8859,7 +9911,7 @@ import { dirname as dirname6, sep as sep2 } from "path";
|
|
|
8859
9911
|
// src/core/wrapWindows.ts
|
|
8860
9912
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync15, readdirSync as readdirSync12, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync9, statSync as statSync8, writeFileSync as writeFileSync14 } from "fs";
|
|
8861
9913
|
import { tmpdir as tmpdir2 } from "os";
|
|
8862
|
-
import { join as
|
|
9914
|
+
import { join as join21 } from "path";
|
|
8863
9915
|
var WINDOW_ID_RE = /^[a-f0-9]{32}$/;
|
|
8864
9916
|
var WINDOW_EVENTS = [
|
|
8865
9917
|
"SessionStart",
|
|
@@ -8872,10 +9924,10 @@ var WINDOW_EVENTS = [
|
|
|
8872
9924
|
"SessionEnd"
|
|
8873
9925
|
];
|
|
8874
9926
|
function windowsDir() {
|
|
8875
|
-
return
|
|
9927
|
+
return join21(deskbuddyHome(), "windows");
|
|
8876
9928
|
}
|
|
8877
9929
|
function bySessionDir() {
|
|
8878
|
-
return
|
|
9930
|
+
return join21(windowsDir(), "by-session");
|
|
8879
9931
|
}
|
|
8880
9932
|
function safeSid(sessionId) {
|
|
8881
9933
|
return String(sessionId).replace(/[^A-Za-z0-9_-]/g, "");
|
|
@@ -8889,7 +9941,7 @@ function atomicWrite(file3, text) {
|
|
|
8889
9941
|
renameSync10(tmp, file3);
|
|
8890
9942
|
}
|
|
8891
9943
|
function windowFile(windowId) {
|
|
8892
|
-
return
|
|
9944
|
+
return join21(windowsDir(), `${windowId}.json`);
|
|
8893
9945
|
}
|
|
8894
9946
|
function writeWindow(windowId, rec) {
|
|
8895
9947
|
if (!WINDOW_ID_RE.test(windowId)) return;
|
|
@@ -8925,7 +9977,7 @@ function removeWindow(windowId) {
|
|
|
8925
9977
|
return;
|
|
8926
9978
|
}
|
|
8927
9979
|
for (const n of names) {
|
|
8928
|
-
const f =
|
|
9980
|
+
const f = join21(bySessionDir(), n);
|
|
8929
9981
|
try {
|
|
8930
9982
|
if (readFileSync14(f, "utf8").trim() === windowId) rmSync9(f, { force: true });
|
|
8931
9983
|
} catch {
|
|
@@ -8936,14 +9988,14 @@ function readBySession(sessionId) {
|
|
|
8936
9988
|
const safe2 = safeSid(sessionId);
|
|
8937
9989
|
if (!safe2) return null;
|
|
8938
9990
|
try {
|
|
8939
|
-
const id = readFileSync14(
|
|
9991
|
+
const id = readFileSync14(join21(bySessionDir(), safe2), "utf8").trim();
|
|
8940
9992
|
return WINDOW_ID_RE.test(id) ? id : null;
|
|
8941
9993
|
} catch {
|
|
8942
9994
|
return null;
|
|
8943
9995
|
}
|
|
8944
9996
|
}
|
|
8945
9997
|
function lastEventFile(windowId) {
|
|
8946
|
-
return
|
|
9998
|
+
return join21(windowsDir(), `${windowId}.last`);
|
|
8947
9999
|
}
|
|
8948
10000
|
function readLastEvent(windowId) {
|
|
8949
10001
|
if (!WINDOW_ID_RE.test(windowId)) return null;
|
|
@@ -8951,239 +10003,33 @@ function readLastEvent(windowId) {
|
|
|
8951
10003
|
const e = JSON.parse(readFileSync14(lastEventFile(windowId), "utf8"));
|
|
8952
10004
|
return e && typeof e.ev === "string" && typeof e.at === "number" ? { ev: e.ev, sid: String(e.sid ?? ""), at: e.at } : null;
|
|
8953
10005
|
} catch {
|
|
8954
|
-
return null;
|
|
8955
|
-
}
|
|
8956
|
-
}
|
|
8957
|
-
function writeLastEvent(windowId, ev, sid, at) {
|
|
8958
|
-
if (!WINDOW_ID_RE.test(windowId) || !WINDOW_EVENTS.includes(ev)) return;
|
|
8959
|
-
ensureDir(windowsDir());
|
|
8960
|
-
atomicWrite(lastEventFile(windowId), JSON.stringify({ ev, sid, at }));
|
|
8961
|
-
}
|
|
8962
|
-
function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
|
|
8963
|
-
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
|
|
8964
|
-
const name = `${windowId.slice(0, 16)}.sock`;
|
|
8965
|
-
const primary = join20(home, "win", name);
|
|
8966
|
-
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
8967
|
-
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
8968
|
-
return join20(tmpdir2(), `deskbuddy-${uid}`, name);
|
|
8969
|
-
}
|
|
8970
|
-
function ensureChannelDir(sockPath) {
|
|
8971
|
-
const dir3 = join20(sockPath, "..");
|
|
8972
|
-
try {
|
|
8973
|
-
mkdirSync15(dir3, { recursive: true, mode: 448 });
|
|
8974
|
-
const st = statSync8(dir3);
|
|
8975
|
-
if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
|
|
8976
|
-
chmodSync2(dir3, 448);
|
|
8977
|
-
return true;
|
|
8978
|
-
} catch {
|
|
8979
|
-
return false;
|
|
8980
|
-
}
|
|
8981
|
-
}
|
|
8982
|
-
|
|
8983
|
-
// src/wrap/answerKeys.ts
|
|
8984
|
-
var KEY_DOWN = "\x1B[B";
|
|
8985
|
-
var KEY_UP = "\x1B[A";
|
|
8986
|
-
var KEY_ENTER = "\r";
|
|
8987
|
-
var ARROW_GAP_MS = 150;
|
|
8988
|
-
var CONFIRM_MS = 4e3;
|
|
8989
|
-
var POLL_MS2 = 150;
|
|
8990
|
-
var FINGERPRINT_CHARS = 40;
|
|
8991
|
-
var FINGERPRINT_TAIL = 30;
|
|
8992
|
-
var FP_SEP = "\u2026";
|
|
8993
|
-
var CURSOR = "[\u276F>\u203A]";
|
|
8994
|
-
function joined(lines3) {
|
|
8995
|
-
return lines3.join("\n");
|
|
8996
|
-
}
|
|
8997
|
-
function squash(s2) {
|
|
8998
|
-
return s2.replace(/[\s\u2500-\u257F]+/g, "");
|
|
8999
|
-
}
|
|
9000
|
-
function basename8(p) {
|
|
9001
|
-
const parts = p.split(/[\\/]+/).filter(Boolean);
|
|
9002
|
-
return parts[parts.length - 1] ?? "";
|
|
9003
|
-
}
|
|
9004
|
-
function displayFingerprint(toolName, input) {
|
|
9005
|
-
const a = input && typeof input === "object" ? input : {};
|
|
9006
|
-
const s2 = (v) => typeof v === "string" ? v : "";
|
|
9007
|
-
const headTail = (v) => {
|
|
9008
|
-
const q = squash(v);
|
|
9009
|
-
return q.length > FINGERPRINT_CHARS + FINGERPRINT_TAIL ? q.slice(0, FINGERPRINT_CHARS) + FP_SEP + q.slice(-FINGERPRINT_TAIL) : q;
|
|
9010
|
-
};
|
|
9011
|
-
if (toolName === "Bash") return headTail(s2(a.command));
|
|
9012
|
-
if (toolName === "Write") {
|
|
9013
|
-
const name = squash(basename8(s2(a.file_path)));
|
|
9014
|
-
const body = squash(s2(a.content)).slice(0, 20);
|
|
9015
|
-
return name ? body ? name + FP_SEP + body : name : "";
|
|
9016
|
-
}
|
|
9017
|
-
if (["Edit", "MultiEdit", "Read", "NotebookEdit"].includes(toolName)) return squash(basename8(s2(a.file_path) || s2(a.notebook_path)));
|
|
9018
|
-
return headTail(Object.values(a).find((v) => typeof v === "string" && v.trim() !== "") ?? "").slice(0, 200);
|
|
9019
|
-
}
|
|
9020
|
-
var SETTLE_MS = 800;
|
|
9021
|
-
async function settled(io, ok) {
|
|
9022
|
-
if (!ok(io.screen())) return false;
|
|
9023
|
-
await io.sleep(SETTLE_MS);
|
|
9024
|
-
return ok(io.screen());
|
|
9025
|
-
}
|
|
9026
|
-
async function waitUntil(io, ok, ms = CONFIRM_MS) {
|
|
9027
|
-
const loops = Math.max(1, Math.ceil(ms / POLL_MS2));
|
|
9028
|
-
for (let i = 0; i < loops; i++) {
|
|
9029
|
-
if (ok(io.screen())) return true;
|
|
9030
|
-
await io.sleep(POLL_MS2);
|
|
9031
|
-
}
|
|
9032
|
-
return ok(io.screen());
|
|
9033
|
-
}
|
|
9034
|
-
function permissionRegion(lines3, toolName) {
|
|
9035
|
-
let q = -1;
|
|
9036
|
-
for (let i = lines3.length - 1; i >= 0; i--) {
|
|
9037
|
-
if (/^\s*Do you want to .+\?\s*$/.test(lines3[i])) {
|
|
9038
|
-
q = i;
|
|
9039
|
-
break;
|
|
9040
|
-
}
|
|
9041
|
-
}
|
|
9042
|
-
if (q < 0 || !toolName) return null;
|
|
9043
|
-
const head = new RegExp(`^\\s*\u25CF\\s*${toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\(`);
|
|
9044
|
-
let h = -1;
|
|
9045
|
-
for (let i = q - 1; i >= 0; i--) {
|
|
9046
|
-
if (head.test(lines3[i])) {
|
|
9047
|
-
h = i;
|
|
9048
|
-
break;
|
|
9049
|
-
}
|
|
9050
|
-
if (/^\s*●/.test(lines3[i])) return null;
|
|
9051
|
-
}
|
|
9052
|
-
if (h < 0) return null;
|
|
9053
|
-
const after = joined(lines3.slice(q + 1, q + 6));
|
|
9054
|
-
if (!new RegExp(`^\\s*(?:${CURSOR}\\s*)?1\\.\\s+Yes\\s*$`, "m").test(after)) return null;
|
|
9055
|
-
if (!/^\s*(?:[❯>›]\s*)?3\.\s+No\b/m.test(after)) return null;
|
|
9056
|
-
return lines3.slice(h, q + 1);
|
|
9057
|
-
}
|
|
9058
|
-
function isPermissionDialog(lines3, toolName, fingerprint2) {
|
|
9059
|
-
if (!fingerprint2) return false;
|
|
9060
|
-
const region = permissionRegion(lines3, toolName);
|
|
9061
|
-
if (!region) return false;
|
|
9062
|
-
const text = squash(joined(region));
|
|
9063
|
-
return fingerprint2.split(FP_SEP).every((part) => !!part && text.includes(squash(part)));
|
|
9064
|
-
}
|
|
9065
|
-
async function answerPermission(io, toolName, fingerprint2, decision) {
|
|
9066
|
-
const ok = (l) => isPermissionDialog(l, toolName, fingerprint2);
|
|
9067
|
-
if (!await settled(io, ok)) return "not_waiting";
|
|
9068
|
-
io.write(decision === "allow" ? "1" : "3");
|
|
9069
|
-
return await waitUntil(io, (l) => !ok(l)) ? "done" : "partial";
|
|
9070
|
-
}
|
|
9071
|
-
var TAB_ROW = /←.*[☐☒].*Submit.*→/;
|
|
9072
|
-
function isQuestionDialog(lines3) {
|
|
9073
|
-
return lines3.some((l) => TAB_ROW.test(l));
|
|
9074
|
-
}
|
|
9075
|
-
function onQuestion(lines3, q) {
|
|
9076
|
-
const start = questionStart(lines3);
|
|
9077
|
-
if (start < 0) return false;
|
|
9078
|
-
const t = squash(q.question);
|
|
9079
|
-
return !!t && squash(joined(lines3.slice(start))).includes(t);
|
|
9080
|
-
}
|
|
9081
|
-
function isReviewPage(lines3) {
|
|
9082
|
-
const t = joined(lines3);
|
|
9083
|
-
return /Review your answers/.test(t) && /Submit answers/.test(t);
|
|
9084
|
-
}
|
|
9085
|
-
function questionStart(lines3) {
|
|
9086
|
-
for (let i = lines3.length - 1; i >= 0; i--) if (TAB_ROW.test(lines3[i])) return i + 1;
|
|
9087
|
-
return -1;
|
|
9088
|
-
}
|
|
9089
|
-
function optionLine(lines3, n, label) {
|
|
9090
|
-
const lab = squash(label).slice(0, 6);
|
|
9091
|
-
const start = questionStart(lines3);
|
|
9092
|
-
if (start < 0) return -1;
|
|
9093
|
-
for (let i = start; i < lines3.length; i++) {
|
|
9094
|
-
const m = lines3[i].match(/^\s*(?:[❯>›]\s*)?[☐☒]?\s*(\d+)\.\s+(.*)$/);
|
|
9095
|
-
if (m && Number(m[1]) === n && squash(m[2]).startsWith(lab)) return i;
|
|
9096
|
-
}
|
|
9097
|
-
return -1;
|
|
9098
|
-
}
|
|
9099
|
-
function checked(lines3, line) {
|
|
9100
|
-
return line >= 0 && /^\s*☒/.test(lines3[line]);
|
|
9101
|
-
}
|
|
9102
|
-
function singleCursor(lines3, q) {
|
|
9103
|
-
const n = q.options.length;
|
|
9104
|
-
const start = questionStart(lines3);
|
|
9105
|
-
if (start < 0) return -1;
|
|
9106
|
-
for (let i = start; i < lines3.length; i++) {
|
|
9107
|
-
const l = lines3[i];
|
|
9108
|
-
const m = l.match(new RegExp(`^\\s*${CURSOR}\\s*(\\d+)\\.\\s+(.*)$`));
|
|
9109
|
-
if (m) {
|
|
9110
|
-
const k = Number(m[1]) - 1;
|
|
9111
|
-
if (k >= 0 && k < n) return squash(m[2]).startsWith(squash(q.options[k].label).slice(0, 6)) ? k : -1;
|
|
9112
|
-
if (k === n) return /^Typesomething/.test(squash(m[2])) ? n : -1;
|
|
9113
|
-
return -1;
|
|
9114
|
-
}
|
|
9115
|
-
if (new RegExp(`^\\s*${CURSOR}\\s*(Next|Submit)\\s*$`).test(l)) return n + 1;
|
|
10006
|
+
return null;
|
|
9116
10007
|
}
|
|
9117
|
-
return -1;
|
|
9118
10008
|
}
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
await io.sleep(ARROW_GAP_MS);
|
|
9124
|
-
}
|
|
10009
|
+
function writeLastEvent(windowId, ev, sid, at) {
|
|
10010
|
+
if (!WINDOW_ID_RE.test(windowId) || !WINDOW_EVENTS.includes(ev)) return;
|
|
10011
|
+
ensureDir(windowsDir());
|
|
10012
|
+
atomicWrite(lastEventFile(windowId), JSON.stringify({ ev, sid, at }));
|
|
9125
10013
|
}
|
|
9126
|
-
|
|
9127
|
-
if (
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
}
|
|
9134
|
-
if (!await settled(io, (l) => onQuestion(l, questions[0]))) return "not_waiting";
|
|
9135
|
-
if (questions.some((q) => q.multiSelect === true) && (!io.cursorLine || io.cursorLine() === null)) return "unsupported";
|
|
9136
|
-
let wrote = false;
|
|
9137
|
-
const fail = () => wrote ? "partial" : "not_waiting";
|
|
9138
|
-
for (let qi = 0; qi < questions.length; qi++) {
|
|
9139
|
-
const q = questions[qi];
|
|
9140
|
-
const n = q.options.length;
|
|
9141
|
-
const sel = [...selections[qi]].sort((a, b) => a - b);
|
|
9142
|
-
if (!await waitUntil(io, (l) => onQuestion(l, q))) return fail();
|
|
9143
|
-
if (q.multiSelect === true) {
|
|
9144
|
-
const lines0 = io.screen();
|
|
9145
|
-
for (let i = 0; i < n; i++) if (checked(lines0, optionLine(lines0, i + 1, q.options[i].label))) return fail();
|
|
9146
|
-
const first = optionLine(lines0, 1, q.options[0].label);
|
|
9147
|
-
if (first < 0 || io.cursorLine?.() !== first) return fail();
|
|
9148
|
-
let cur = 0;
|
|
9149
|
-
for (const t of sel) {
|
|
9150
|
-
await move(io, t - cur);
|
|
9151
|
-
cur = t;
|
|
9152
|
-
const onTarget = await waitUntil(io, (l) => {
|
|
9153
|
-
const ln = optionLine(l, t + 1, q.options[t].label);
|
|
9154
|
-
return ln >= 0 && io.cursorLine?.() === ln;
|
|
9155
|
-
});
|
|
9156
|
-
if (!onTarget) return fail();
|
|
9157
|
-
io.write(KEY_ENTER);
|
|
9158
|
-
wrote = true;
|
|
9159
|
-
if (!await waitUntil(io, (l) => checked(l, optionLine(l, t + 1, q.options[t].label)))) return fail();
|
|
9160
|
-
}
|
|
9161
|
-
await move(io, n + 1 - cur);
|
|
9162
|
-
if (!await waitUntil(io, (l) => questionStart(l) >= 0 && new RegExp(`^\\s*${CURSOR}\\s*(Submit|Next)\\s*$`, "m").test(joined(l.slice(questionStart(l)))))) return fail();
|
|
9163
|
-
io.write(KEY_ENTER);
|
|
9164
|
-
wrote = true;
|
|
9165
|
-
} else {
|
|
9166
|
-
const t = sel[0];
|
|
9167
|
-
const cur = singleCursor(io.screen(), q);
|
|
9168
|
-
if (cur < 0) return fail();
|
|
9169
|
-
await move(io, t - cur);
|
|
9170
|
-
if (!await waitUntil(io, (l) => singleCursor(l, q) === t && optionLine(l, t + 1, q.options[t].label) >= 0)) return fail();
|
|
9171
|
-
io.write(KEY_ENTER);
|
|
9172
|
-
wrote = true;
|
|
9173
|
-
}
|
|
9174
|
-
const next = questions[qi + 1];
|
|
9175
|
-
const moved = await waitUntil(io, (l) => next ? onQuestion(l, next) : isReviewPage(l) || !isQuestionDialog(l));
|
|
9176
|
-
if (!moved) return fail();
|
|
9177
|
-
}
|
|
9178
|
-
const s2 = io.screen();
|
|
9179
|
-
if (!isReviewPage(s2)) return isQuestionDialog(s2) ? fail() : "done";
|
|
9180
|
-
if (!new RegExp(`^\\s*${CURSOR}\\s*1\\.\\s+Submit answers`, "m").test(joined(s2))) return fail();
|
|
9181
|
-
io.write(KEY_ENTER);
|
|
9182
|
-
return await waitUntil(io, (l) => !isReviewPage(l) && !isQuestionDialog(l)) ? "done" : "partial";
|
|
10014
|
+
function channelPath(windowId, platform = process.platform, home = deskbuddyHome()) {
|
|
10015
|
+
if (platform === "win32") return `\\\\.\\pipe\\deskbuddy-${windowId}`;
|
|
10016
|
+
const name = `${windowId.slice(0, 16)}.sock`;
|
|
10017
|
+
const primary = join21(home, "win", name);
|
|
10018
|
+
if (Buffer.byteLength(primary) <= 103) return primary;
|
|
10019
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
10020
|
+
return join21(tmpdir2(), `deskbuddy-${uid}`, name);
|
|
9183
10021
|
}
|
|
9184
|
-
function
|
|
9185
|
-
|
|
9186
|
-
|
|
10022
|
+
function ensureChannelDir(sockPath) {
|
|
10023
|
+
const dir3 = join21(sockPath, "..");
|
|
10024
|
+
try {
|
|
10025
|
+
mkdirSync15(dir3, { recursive: true, mode: 448 });
|
|
10026
|
+
const st = statSync8(dir3);
|
|
10027
|
+
if (typeof process.getuid === "function" && st.uid !== process.getuid()) return false;
|
|
10028
|
+
chmodSync2(dir3, 448);
|
|
10029
|
+
return true;
|
|
10030
|
+
} catch {
|
|
10031
|
+
return false;
|
|
10032
|
+
}
|
|
9187
10033
|
}
|
|
9188
10034
|
|
|
9189
10035
|
// src/wrap/channel.ts
|
|
@@ -9211,6 +10057,20 @@ function parseAnswer(m) {
|
|
|
9211
10057
|
if (a.decision !== "allow" && a.decision !== "deny") return null;
|
|
9212
10058
|
return { instructionId, sessionId, expireAt, answer: { kind: "permission", toolName, fingerprint: fingerprint2, decision: a.decision } };
|
|
9213
10059
|
}
|
|
10060
|
+
if (a.kind === "qoder-read" || a.kind === "qoder-key") {
|
|
10061
|
+
const toolName = toolNameOf(a.toolName);
|
|
10062
|
+
const fp = a.fingerprint === void 0 ? void 0 : str4(a.fingerprint, 4e3);
|
|
10063
|
+
if (!toolName || fp === null) return null;
|
|
10064
|
+
const f = fp ? { fingerprint: fp } : {};
|
|
10065
|
+
if (a.kind === "qoder-read") return { instructionId, sessionId, expireAt, answer: { kind: "qoder-read", toolName, ...f } };
|
|
10066
|
+
const labels = Array.isArray(a.labels) ? a.labels : [];
|
|
10067
|
+
if (labels.length < 2 || labels.length > 16 || !labels.every((l) => str4(l, 1e3) !== null)) return null;
|
|
10068
|
+
const n = a.n;
|
|
10069
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > labels.length) return null;
|
|
10070
|
+
const box = a.box;
|
|
10071
|
+
if (typeof box !== "number" || !Number.isInteger(box) || box < 1) return null;
|
|
10072
|
+
return { instructionId, sessionId, expireAt, answer: { kind: "qoder-key", toolName, ...f, labels, n, box } };
|
|
10073
|
+
}
|
|
9214
10074
|
if (a.prompt !== void 0 && a.prompt !== "hooks") return null;
|
|
9215
10075
|
const hooks = a.prompt === "hooks" ? { prompt: "hooks" } : {};
|
|
9216
10076
|
if (a.kind === "trust") {
|
|
@@ -9319,18 +10179,19 @@ function parseRequest(line) {
|
|
|
9319
10179
|
}
|
|
9320
10180
|
var HEX32 = /^[0-9a-f]{32}$/;
|
|
9321
10181
|
function replyMac(secret, nonceC, r) {
|
|
9322
|
-
|
|
10182
|
+
const extra = r.options !== void 0 || r.box !== void 0 ? [canonicalJson({ options: r.options, box: r.box })] : [];
|
|
10183
|
+
return wakeMac(secret, "r", nonceC, r.instructionId, r.status, r.reason ?? "", ...extra);
|
|
9323
10184
|
}
|
|
9324
10185
|
function startChannelServer(path, secret, onRequest, platform = process.platform, onAnswer) {
|
|
9325
10186
|
return new Promise((resolve10) => {
|
|
9326
10187
|
const server = createServer3((sock) => {
|
|
9327
|
-
let
|
|
10188
|
+
let open3 = true;
|
|
9328
10189
|
let phase = "hello";
|
|
9329
10190
|
let nonceC = "";
|
|
9330
10191
|
let nonceS = "";
|
|
9331
10192
|
sock.on("error", () => {
|
|
9332
10193
|
});
|
|
9333
|
-
sock.on("close", () =>
|
|
10194
|
+
sock.on("close", () => open3 = false);
|
|
9334
10195
|
const authTimer = setTimeout(() => {
|
|
9335
10196
|
if (phase === "hello") sock.destroy();
|
|
9336
10197
|
}, 5e3);
|
|
@@ -9375,7 +10236,7 @@ function startChannelServer(path, secret, onRequest, platform = process.platform
|
|
|
9375
10236
|
return;
|
|
9376
10237
|
}
|
|
9377
10238
|
phase = "done";
|
|
9378
|
-
onRequest(req, reply, () =>
|
|
10239
|
+
onRequest(req, reply, () => open3);
|
|
9379
10240
|
} catch {
|
|
9380
10241
|
sock.destroy();
|
|
9381
10242
|
}
|
|
@@ -9615,10 +10476,10 @@ function toolOfMessage(message) {
|
|
|
9615
10476
|
}
|
|
9616
10477
|
function pendingCall(text, toolName) {
|
|
9617
10478
|
const { calls, done } = scanTranscript(text);
|
|
9618
|
-
const
|
|
9619
|
-
const last =
|
|
10479
|
+
const open3 = calls.filter((c) => !done.has(c.callId));
|
|
10480
|
+
const last = open3[open3.length - 1];
|
|
9620
10481
|
if (!last || last.name !== toolName) return null;
|
|
9621
|
-
if (
|
|
10482
|
+
if (open3.filter((c) => c.name === toolName).length > 1) return null;
|
|
9622
10483
|
return last;
|
|
9623
10484
|
}
|
|
9624
10485
|
function selectionsOf(input, lists) {
|
|
@@ -9896,6 +10757,62 @@ function defaultSend(windowId, rec, req) {
|
|
|
9896
10757
|
return sendAnswer(channelPath(windowId), secret, req, { finalTimeoutMs: req.answer.kind === "probe" ? 5e3 : 60 * 1e3 });
|
|
9897
10758
|
}
|
|
9898
10759
|
|
|
10760
|
+
// src/daemon/qoderScreen.ts
|
|
10761
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
10762
|
+
|
|
10763
|
+
// src/daemon/window.ts
|
|
10764
|
+
function windowForSession(sessionId, tool) {
|
|
10765
|
+
const id = readBySession(sessionId);
|
|
10766
|
+
if (!id) return null;
|
|
10767
|
+
const rec = readWindow(id);
|
|
10768
|
+
if (!rec || !isAlive(rec.pid)) return null;
|
|
10769
|
+
if (tool && rec.tool && rec.tool !== tool) return null;
|
|
10770
|
+
return id;
|
|
10771
|
+
}
|
|
10772
|
+
|
|
10773
|
+
// src/daemon/qoderScreen.ts
|
|
10774
|
+
var READ_TIMEOUT_MS = 5e3;
|
|
10775
|
+
var PRESS_TIMEOUT_MS = 15e3;
|
|
10776
|
+
var EXPIRE_MS = 3e4;
|
|
10777
|
+
var defaultDeps = {
|
|
10778
|
+
windowFor: windowForSession,
|
|
10779
|
+
readWindow,
|
|
10780
|
+
send: (windowId, req, finalTimeoutMs) => sendAnswer(channelPath(windowId), windowSecret(windowId), req, { finalTimeoutMs })
|
|
10781
|
+
};
|
|
10782
|
+
function qoderScreen(d = defaultDeps) {
|
|
10783
|
+
const target = (sessionId) => {
|
|
10784
|
+
const id = d.windowFor(sessionId, "qoder_cn");
|
|
10785
|
+
if (!id) return null;
|
|
10786
|
+
const rec = d.readWindow(id);
|
|
10787
|
+
return rec && rec.tool === "qoder_cn" && rec.answer !== true ? id : null;
|
|
10788
|
+
};
|
|
10789
|
+
const req = (sessionId, answer) => ({
|
|
10790
|
+
instructionId: randomUUID2().replace(/-/g, ""),
|
|
10791
|
+
sessionId,
|
|
10792
|
+
expireAt: Date.now() + EXPIRE_MS,
|
|
10793
|
+
answer
|
|
10794
|
+
});
|
|
10795
|
+
return {
|
|
10796
|
+
async read(sessionId, toolName, fingerprint2) {
|
|
10797
|
+
const win = target(sessionId);
|
|
10798
|
+
if (!win) return null;
|
|
10799
|
+
const r = await d.send(win, req(sessionId, { kind: "qoder-read", toolName, ...fingerprint2 ? { fingerprint: fingerprint2 } : {} }), READ_TIMEOUT_MS);
|
|
10800
|
+
if (!r || r.status !== "started" || !Array.isArray(r.options) || typeof r.box !== "number" || !Number.isInteger(r.box) || r.box < 1) return null;
|
|
10801
|
+
const labels = r.options.filter((x) => typeof x === "string" && x.length > 0);
|
|
10802
|
+
return labels.length === r.options.length && labels.length >= 2 ? { labels, box: r.box } : null;
|
|
10803
|
+
},
|
|
10804
|
+
async press(sessionId, box, n) {
|
|
10805
|
+
const win = target(sessionId);
|
|
10806
|
+
if (!win) return "not_written";
|
|
10807
|
+
const answer = { kind: "qoder-key", toolName: box.toolName, ...box.fingerprint ? { fingerprint: box.fingerprint } : {}, labels: box.labels, n, box: box.box };
|
|
10808
|
+
const r = await d.send(win, req(sessionId, answer), PRESS_TIMEOUT_MS);
|
|
10809
|
+
if (r?.status === "started") return "done";
|
|
10810
|
+
if (!r || r.status === "rejected" || r.status === "not_here") return "not_written";
|
|
10811
|
+
return "uncertain";
|
|
10812
|
+
}
|
|
10813
|
+
};
|
|
10814
|
+
}
|
|
10815
|
+
|
|
9899
10816
|
// src/daemon/codebuddyTerminal.ts
|
|
9900
10817
|
import { closeSync as closeSync8, openSync as openSync8, readSync as readSync7, statSync as statSync10 } from "fs";
|
|
9901
10818
|
var ANNOUNCE_TTL_MS = 5 * 1e3;
|
|
@@ -10172,12 +11089,12 @@ var CodebuddyTerminal = class {
|
|
|
10172
11089
|
|
|
10173
11090
|
// src/wrap/trustPrompt.ts
|
|
10174
11091
|
import { existsSync as existsSync10, realpathSync as realpathSync7, statSync as statSync11 } from "fs";
|
|
10175
|
-
import { homedir as
|
|
10176
|
-
import { dirname as dirname7, join as
|
|
11092
|
+
import { homedir as homedir7 } from "os";
|
|
11093
|
+
import { dirname as dirname7, join as join22, resolve as resolve7 } from "path";
|
|
10177
11094
|
var TRUST_TOOLS = /* @__PURE__ */ new Set(["claude_code", "workbuddy", "codex"]);
|
|
10178
11095
|
var CURSOR_CHARS = "\u276F>\u203A";
|
|
10179
11096
|
var BOX = /[─-╿|]/g;
|
|
10180
|
-
function
|
|
11097
|
+
function squash3(s2) {
|
|
10181
11098
|
return s2.replace(BOX, "").replace(/\s+/g, "");
|
|
10182
11099
|
}
|
|
10183
11100
|
function squashLines(lines3) {
|
|
@@ -10330,7 +11247,7 @@ function pathShown(v, forms) {
|
|
|
10330
11247
|
const body = v.body.toLowerCase();
|
|
10331
11248
|
const breaks = v.breaks ?? [];
|
|
10332
11249
|
return forms.some((f) => {
|
|
10333
|
-
const q =
|
|
11250
|
+
const q = squash3(f).toLowerCase();
|
|
10334
11251
|
if (!q) return false;
|
|
10335
11252
|
for (let i = body.indexOf(q); i >= 0; i = body.indexOf(q, i + 1)) {
|
|
10336
11253
|
const end = i + q.length;
|
|
@@ -10339,7 +11256,7 @@ function pathShown(v, forms) {
|
|
|
10339
11256
|
return false;
|
|
10340
11257
|
});
|
|
10341
11258
|
}
|
|
10342
|
-
function trustCwdForms(cwd, real2 = realpathSync7, home =
|
|
11259
|
+
function trustCwdForms(cwd, real2 = realpathSync7, home = homedir7()) {
|
|
10343
11260
|
const out = /* @__PURE__ */ new Set();
|
|
10344
11261
|
const add = (p) => {
|
|
10345
11262
|
if (!p) return;
|
|
@@ -10363,7 +11280,7 @@ function gitStat(p) {
|
|
|
10363
11280
|
return null;
|
|
10364
11281
|
}
|
|
10365
11282
|
}
|
|
10366
|
-
function findRepoScope(tool, cwd, stat2 = gitStat, home =
|
|
11283
|
+
function findRepoScope(tool, cwd, stat2 = gitStat, home = homedir7(), real2 = realpathSync7) {
|
|
10367
11284
|
if (tool !== "claude_code" && tool !== "codex" || !cwd) return null;
|
|
10368
11285
|
const logical = resolve7(cwd);
|
|
10369
11286
|
const a = scanRepo(logical, stat2, home);
|
|
@@ -10381,7 +11298,7 @@ function scanRepo(start, stat2, home) {
|
|
|
10381
11298
|
let d = start.replace(/[\\/]+$/, "") || start;
|
|
10382
11299
|
let aboveHome = false;
|
|
10383
11300
|
for (let i = 0; i < 64; i++) {
|
|
10384
|
-
const g = stat2(
|
|
11301
|
+
const g = stat2(join22(d, ".git"));
|
|
10385
11302
|
if (g) {
|
|
10386
11303
|
if (aboveHome) return { kind: "unknown" };
|
|
10387
11304
|
if (g === "file") return { kind: "worktree", at: d };
|
|
@@ -10421,11 +11338,11 @@ function trustAnswerable(v, forms) {
|
|
|
10421
11338
|
if (v.kind === "hooks") return true;
|
|
10422
11339
|
return !widenedBeyond(v, forms) && pathShown(v, forms);
|
|
10423
11340
|
}
|
|
10424
|
-
var
|
|
10425
|
-
var
|
|
11341
|
+
var SETTLE_MS3 = 800;
|
|
11342
|
+
var CONFIRM_MS3 = 4e3;
|
|
10426
11343
|
var POLL_MS4 = 150;
|
|
10427
11344
|
var ARROW_GAP_MS2 = 150;
|
|
10428
|
-
async function
|
|
11345
|
+
async function waitUntil3(io, ok, ms = CONFIRM_MS3) {
|
|
10429
11346
|
const loops = Math.max(1, Math.ceil(ms / POLL_MS4));
|
|
10430
11347
|
for (let i = 0; i < loops; i++) {
|
|
10431
11348
|
if (ok(io.screen())) return true;
|
|
@@ -10441,7 +11358,7 @@ async function answerTrust(io, tool, decision, forms, kind = "trust", repoRoot)
|
|
|
10441
11358
|
};
|
|
10442
11359
|
const v0 = view(io.screen());
|
|
10443
11360
|
if (!v0) return "not_waiting";
|
|
10444
|
-
await io.sleep(
|
|
11361
|
+
await io.sleep(SETTLE_MS3);
|
|
10445
11362
|
const v = view(io.screen());
|
|
10446
11363
|
if (!v || v.cursor !== 0) return "not_waiting";
|
|
10447
11364
|
const role = decision === "allow" ? "folder" : "no";
|
|
@@ -10456,7 +11373,7 @@ async function answerTrust(io, tool, decision, forms, kind = "trust", repoRoot)
|
|
|
10456
11373
|
wrote = true;
|
|
10457
11374
|
await io.sleep(ARROW_GAP_MS2);
|
|
10458
11375
|
}
|
|
10459
|
-
const onTarget = await
|
|
11376
|
+
const onTarget = await waitUntil3(io, (l) => {
|
|
10460
11377
|
const w = view(l);
|
|
10461
11378
|
return !!w && w.cursor >= 0 && w.options[w.cursor].role === role;
|
|
10462
11379
|
});
|
|
@@ -10465,7 +11382,7 @@ async function answerTrust(io, tool, decision, forms, kind = "trust", repoRoot)
|
|
|
10465
11382
|
} finally {
|
|
10466
11383
|
io.hold?.(false);
|
|
10467
11384
|
}
|
|
10468
|
-
return await
|
|
11385
|
+
return await waitUntil3(io, (l) => !inspectTrust(l, tool, kind)) ? "done" : "partial";
|
|
10469
11386
|
}
|
|
10470
11387
|
function trustWaiting(lines3, tool, kind = "trust") {
|
|
10471
11388
|
return inspectTrust(lines3, tool, kind) !== null;
|
|
@@ -11972,9 +12889,9 @@ function codexQuestionInput(p) {
|
|
|
11972
12889
|
|
|
11973
12890
|
// src/core/codexRemote.ts
|
|
11974
12891
|
import { mkdirSync as mkdirSync16, renameSync as renameSync11, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "fs";
|
|
11975
|
-
import { join as
|
|
12892
|
+
import { join as join23 } from "path";
|
|
11976
12893
|
function codexRemotePath() {
|
|
11977
|
-
return
|
|
12894
|
+
return join23(deskbuddyHome(), "codex-remote.json");
|
|
11978
12895
|
}
|
|
11979
12896
|
function writeCodexRemote(threads, pid = process.pid, now = Date.now()) {
|
|
11980
12897
|
try {
|
|
@@ -11992,71 +12909,9 @@ function removeCodexRemote() {
|
|
|
11992
12909
|
}
|
|
11993
12910
|
}
|
|
11994
12911
|
|
|
11995
|
-
// src/wrap/screen.ts
|
|
11996
|
-
var ESC = "\x1B";
|
|
11997
|
-
var PASTE_BEGIN = ESC + "[200~";
|
|
11998
|
-
var PASTE_END = ESC + "[201~";
|
|
11999
|
-
function sanitize(text) {
|
|
12000
|
-
return String(text).replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
|
|
12001
|
-
}
|
|
12002
|
-
var MAX_INJECT_CHARS = 800;
|
|
12003
|
-
var TEXT_TOO_LONG = "text_too_long";
|
|
12004
|
-
function oneLine(text) {
|
|
12005
|
-
return sanitize(text).replace(/\s*\n\s*/g, " ").trim();
|
|
12006
|
-
}
|
|
12007
|
-
function tooLongToInject(text) {
|
|
12008
|
-
return oneLine(text).length > MAX_INJECT_CHARS;
|
|
12009
|
-
}
|
|
12010
|
-
function prepare(text) {
|
|
12011
|
-
return " " + oneLine(text);
|
|
12012
|
-
}
|
|
12013
|
-
var TRUST_RE = /trust (the files in )?this folder|Yes, I trust this folder/i;
|
|
12014
|
-
var PLACEHOLDER_RE = /^Try ".*"$/;
|
|
12015
|
-
function isTerminalReport(s2) {
|
|
12016
|
-
if (!s2) return true;
|
|
12017
|
-
const rest = s2.replace(/\x1b\[[IO]/g, "").replace(/\x1b\[\d+;\d+R/g, "").replace(/\x1b\[[?>][\d;]*\$?[a-zA-Z]/g, "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1bP[^\x1b]*\x1b\\/g, "");
|
|
12018
|
-
return rest === "";
|
|
12019
|
-
}
|
|
12020
|
-
var PERMISSION_RE = /Do you want to (proceed|make|create|run|allow)|[❯>]\s*1\. Yes/;
|
|
12021
|
-
function inspectLines(lines3, undimmed) {
|
|
12022
|
-
const text = lines3.join("\n");
|
|
12023
|
-
const trust = TRUST_RE.test(text);
|
|
12024
|
-
const permission = PERMISSION_RE.test(text);
|
|
12025
|
-
const rules = [];
|
|
12026
|
-
lines3.forEach((l, i) => {
|
|
12027
|
-
if (/^─{20,}/.test(l.trim())) rules.push(i);
|
|
12028
|
-
});
|
|
12029
|
-
let input = null;
|
|
12030
|
-
if (rules.length >= 2) {
|
|
12031
|
-
const a = rules[rules.length - 2];
|
|
12032
|
-
const b = rules[rules.length - 1];
|
|
12033
|
-
const src = undimmed && undimmed.length === lines3.length ? undimmed : lines3;
|
|
12034
|
-
input = src.slice(a + 1, b).map((l) => l.trimEnd()).join("\n").replace(/^\s*[❯>]\s?/, "").trimEnd();
|
|
12035
|
-
}
|
|
12036
|
-
const v = { permission, input };
|
|
12037
|
-
if (trust) v.trust = true;
|
|
12038
|
-
if (input !== null && PLACEHOLDER_RE.test(input.trim())) v.placeholder = true;
|
|
12039
|
-
return v;
|
|
12040
|
-
}
|
|
12041
|
-
function boxMatches(view, body) {
|
|
12042
|
-
if (view.input === null || view.permission) return false;
|
|
12043
|
-
const flat2 = (x) => x.replace(/\s+/g, "");
|
|
12044
|
-
return flat2(view.input) === flat2(body) || /\[Pasted text #\d+/.test(view.input);
|
|
12045
|
-
}
|
|
12046
|
-
|
|
12047
|
-
// src/daemon/window.ts
|
|
12048
|
-
function windowForSession(sessionId, tool) {
|
|
12049
|
-
const id = readBySession(sessionId);
|
|
12050
|
-
if (!id) return null;
|
|
12051
|
-
const rec = readWindow(id);
|
|
12052
|
-
if (!rec || !isAlive(rec.pid)) return null;
|
|
12053
|
-
if (tool && rec.tool && rec.tool !== tool) return null;
|
|
12054
|
-
return id;
|
|
12055
|
-
}
|
|
12056
|
-
|
|
12057
12912
|
// src/core/interrupt.ts
|
|
12058
12913
|
import { closeSync as closeSync9, openSync as openSync9, readdirSync as readdirSync13, readSync as readSync8, statSync as statSync12 } from "fs";
|
|
12059
|
-
import { dirname as dirname8, join as
|
|
12914
|
+
import { dirname as dirname8, join as join24 } from "path";
|
|
12060
12915
|
var INTERRUPT_TOOLS = ["claude_code", "qoder_cn", "qoder"];
|
|
12061
12916
|
function transcriptHome(tool) {
|
|
12062
12917
|
if (tool === "claude_code") return claudeHome();
|
|
@@ -12162,17 +13017,17 @@ function claudeTranscriptFile(sessionId, cwd, home = claudeHome()) {
|
|
|
12162
13017
|
}
|
|
12163
13018
|
};
|
|
12164
13019
|
if (cwd) {
|
|
12165
|
-
const f =
|
|
13020
|
+
const f = join24(home, "projects", projectDirName(cwd), name);
|
|
12166
13021
|
if (exists(f)) return f;
|
|
12167
13022
|
}
|
|
12168
13023
|
let dirs = [];
|
|
12169
13024
|
try {
|
|
12170
|
-
dirs = readdirSync13(
|
|
13025
|
+
dirs = readdirSync13(join24(home, "projects"));
|
|
12171
13026
|
} catch {
|
|
12172
13027
|
return null;
|
|
12173
13028
|
}
|
|
12174
13029
|
for (const d of dirs) {
|
|
12175
|
-
const f =
|
|
13030
|
+
const f = join24(home, "projects", d, name);
|
|
12176
13031
|
if (exists(f)) return f;
|
|
12177
13032
|
}
|
|
12178
13033
|
return null;
|
|
@@ -12190,12 +13045,12 @@ function shouldSettleInterrupt(st, markerAt, now) {
|
|
|
12190
13045
|
}
|
|
12191
13046
|
|
|
12192
13047
|
// src/daemon/qoderAppDb.ts
|
|
12193
|
-
import { execFile as
|
|
13048
|
+
import { execFile as execFile5 } from "child_process";
|
|
12194
13049
|
import { createHash as createHash4 } from "crypto";
|
|
12195
13050
|
import { existsSync as existsSync11 } from "fs";
|
|
12196
|
-
import { access, open, readdir, stat } from "fs/promises";
|
|
13051
|
+
import { access, open as open2, readdir, stat } from "fs/promises";
|
|
12197
13052
|
import { createRequire as createRequire3 } from "module";
|
|
12198
|
-
import { basename as basename9, dirname as dirname9, isAbsolute as isAbsolute4, join as
|
|
13053
|
+
import { basename as basename9, dirname as dirname9, isAbsolute as isAbsolute4, join as join25 } from "path";
|
|
12199
13054
|
var QODER_APP_TOOLS = ["qoder_cn", "qoder"];
|
|
12200
13055
|
var DB_SCRIPT = String.raw`
|
|
12201
13056
|
const { DatabaseSync } = require('node:sqlite');
|
|
@@ -12272,7 +13127,7 @@ function qoderConfigDir(tool) {
|
|
|
12272
13127
|
}
|
|
12273
13128
|
async function findTranscript(configDir, sessionId) {
|
|
12274
13129
|
if (!SESSION_UUID_RE.test(sessionId)) return null;
|
|
12275
|
-
const root =
|
|
13130
|
+
const root = join25(configDir, "projects");
|
|
12276
13131
|
let dirs;
|
|
12277
13132
|
try {
|
|
12278
13133
|
dirs = await readdir(root);
|
|
@@ -12280,7 +13135,7 @@ async function findTranscript(configDir, sessionId) {
|
|
|
12280
13135
|
return null;
|
|
12281
13136
|
}
|
|
12282
13137
|
for (const d of dirs) {
|
|
12283
|
-
const p =
|
|
13138
|
+
const p = join25(root, d, `${sessionId}.jsonl`);
|
|
12284
13139
|
try {
|
|
12285
13140
|
await access(p);
|
|
12286
13141
|
return p;
|
|
@@ -12291,7 +13146,7 @@ async function findTranscript(configDir, sessionId) {
|
|
|
12291
13146
|
}
|
|
12292
13147
|
var SLICE2 = 64 * 1024;
|
|
12293
13148
|
async function readSlice2(p, from) {
|
|
12294
|
-
const fh = await
|
|
13149
|
+
const fh = await open2(p, "r");
|
|
12295
13150
|
try {
|
|
12296
13151
|
const size = (await fh.stat()).size;
|
|
12297
13152
|
const len = Math.min(size, SLICE2);
|
|
@@ -12373,7 +13228,7 @@ var defaultQoderAppDeps = () => ({
|
|
|
12373
13228
|
},
|
|
12374
13229
|
runElectron: (bin, script, input) => new Promise((resolve10, reject) => {
|
|
12375
13230
|
const env = { ...process.env, ELECTRON_RUN_AS_NODE: "1" };
|
|
12376
|
-
const c =
|
|
13231
|
+
const c = execFile5(
|
|
12377
13232
|
bin,
|
|
12378
13233
|
["-e", script],
|
|
12379
13234
|
{ env, timeout: 15e3, windowsHide: true, maxBuffer: 1024 * 1024 },
|
|
@@ -12852,17 +13707,17 @@ function startWakeServer(path, hub, secret, platform = process.platform) {
|
|
|
12852
13707
|
|
|
12853
13708
|
// src/core/appended.ts
|
|
12854
13709
|
import { chmodSync as chmodSync4, mkdirSync as mkdirSync17, readFileSync as readFileSync16, renameSync as renameSync12, rmSync as rmSync13, writeFileSync as writeFileSync16 } from "fs";
|
|
12855
|
-
import { join as
|
|
13710
|
+
import { join as join26 } from "path";
|
|
12856
13711
|
var APPENDED_MAX_ITEMS = 5;
|
|
12857
13712
|
var APPENDED_TEXT_MAX = 500;
|
|
12858
13713
|
var APPENDED_RESULT_MAX = 800;
|
|
12859
13714
|
var APPENDED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
12860
13715
|
function appendedDir() {
|
|
12861
|
-
return
|
|
13716
|
+
return join26(deskbuddyHome(), "appended");
|
|
12862
13717
|
}
|
|
12863
13718
|
function appendedPath(sessionId) {
|
|
12864
13719
|
const safe2 = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
12865
|
-
return safe2 ?
|
|
13720
|
+
return safe2 ? join26(appendedDir(), `${safe2}.json`) : null;
|
|
12866
13721
|
}
|
|
12867
13722
|
function readAppended(sessionId) {
|
|
12868
13723
|
const file3 = appendedPath(sessionId);
|
|
@@ -12892,13 +13747,13 @@ function writeAppended(sessionId, item) {
|
|
|
12892
13747
|
var RUNNING_TTL_MS = 60 * 60 * 1e3;
|
|
12893
13748
|
function runningPath(sessionId) {
|
|
12894
13749
|
const safe2 = sessionId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
12895
|
-
return safe2 ?
|
|
13750
|
+
return safe2 ? join26(deskbuddyHome(), "running", `${safe2}.json`) : null;
|
|
12896
13751
|
}
|
|
12897
13752
|
function writeRunning(sessionId, now = Date.now()) {
|
|
12898
13753
|
const file3 = runningPath(sessionId);
|
|
12899
13754
|
if (!file3) return;
|
|
12900
13755
|
try {
|
|
12901
|
-
mkdirSync17(
|
|
13756
|
+
mkdirSync17(join26(deskbuddyHome(), "running"), { recursive: true, mode: 448 });
|
|
12902
13757
|
writeFileSync16(`${file3}.tmp`, JSON.stringify({ at: now, pid: process.pid }), { encoding: "utf8", mode: 384 });
|
|
12903
13758
|
renameSync12(`${file3}.tmp`, file3);
|
|
12904
13759
|
} catch {
|
|
@@ -12946,7 +13801,7 @@ function resultSummary(tool, stdout6) {
|
|
|
12946
13801
|
|
|
12947
13802
|
// src/core/dshMailbox.ts
|
|
12948
13803
|
import { chmodSync as chmodSync5, existsSync as existsSync12, mkdirSync as mkdirSync18, readdirSync as readdirSync14, readFileSync as readFileSync17, renameSync as renameSync13, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "fs";
|
|
12949
|
-
import { join as
|
|
13804
|
+
import { join as join27 } from "path";
|
|
12950
13805
|
var DSH_HEARTBEAT_FRESH_MS = 5 * 1e3;
|
|
12951
13806
|
var DSH_ACK_TIMEOUT_MS = 8 * 1e3;
|
|
12952
13807
|
var ACK_POLL_MS = 200;
|
|
@@ -12957,10 +13812,10 @@ var REVOKE_RETRY_MS = 50;
|
|
|
12957
13812
|
var SID_RE3 = /^[A-Za-z0-9_-]{1,128}$/;
|
|
12958
13813
|
var IID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
12959
13814
|
function dshMailboxDir(home = deskbuddyHome(), channel = "dsh") {
|
|
12960
|
-
return
|
|
13815
|
+
return join27(home, channel);
|
|
12961
13816
|
}
|
|
12962
13817
|
function sub(home, name, channel = "dsh") {
|
|
12963
|
-
return
|
|
13818
|
+
return join27(dshMailboxDir(home, channel), name);
|
|
12964
13819
|
}
|
|
12965
13820
|
function ensureDir2(p) {
|
|
12966
13821
|
mkdirSync18(p, { recursive: true, mode: 448 });
|
|
@@ -13000,7 +13855,7 @@ function liveHeartbeats(sessionId, d0 = {}) {
|
|
|
13000
13855
|
if (!/^\d+\.json$/.test(n)) continue;
|
|
13001
13856
|
let hb;
|
|
13002
13857
|
try {
|
|
13003
|
-
hb = JSON.parse(readFileSync17(
|
|
13858
|
+
hb = JSON.parse(readFileSync17(join27(dir3, n), "utf8"));
|
|
13004
13859
|
} catch {
|
|
13005
13860
|
continue;
|
|
13006
13861
|
}
|
|
@@ -13024,8 +13879,8 @@ async function dshDeliver(sessionId, msg, d0 = {}) {
|
|
|
13024
13879
|
const ackDir = sub(d.home, "ack", d.channel);
|
|
13025
13880
|
ensureDir2(inbox);
|
|
13026
13881
|
ensureDir2(ackDir);
|
|
13027
|
-
const letter =
|
|
13028
|
-
const ackPath =
|
|
13882
|
+
const letter = join27(inbox, `${sessionId}.${msg.instructionId}.json`);
|
|
13883
|
+
const ackPath = join27(ackDir, `${msg.instructionId}.json`);
|
|
13029
13884
|
if (existsSync12(letter)) return { ok: false, reason: "inbox_busy" };
|
|
13030
13885
|
const writtenAt = d.now();
|
|
13031
13886
|
writeJson(letter, { instructionId: msg.instructionId, text: msg.text, at: writtenAt, ...d0.addressed ? { pid: hb.pid } : {} });
|
|
@@ -13098,7 +13953,7 @@ function clearDir(inbox) {
|
|
|
13098
13953
|
let n = 0;
|
|
13099
13954
|
for (const f of names) {
|
|
13100
13955
|
try {
|
|
13101
|
-
rmSync14(
|
|
13956
|
+
rmSync14(join27(inbox, f), { force: true });
|
|
13102
13957
|
n++;
|
|
13103
13958
|
} catch {
|
|
13104
13959
|
}
|
|
@@ -13164,7 +14019,7 @@ function defaultCodexAppServers() {
|
|
|
13164
14019
|
}
|
|
13165
14020
|
function opencodeReady() {
|
|
13166
14021
|
const mod = ocPluginModule(installedHookPath());
|
|
13167
|
-
return isOcPluginInstalled(opencodePluginPath()) && existsSync13(mod) && existsSync13(
|
|
14022
|
+
return isOcPluginInstalled(opencodePluginPath()) && existsSync13(mod) && existsSync13(join28(dirname10(mod), "ocTranslate.mjs"));
|
|
13168
14023
|
}
|
|
13169
14024
|
var INTERNAL_PROMPT_MARK = "[DeskBuddy]";
|
|
13170
14025
|
function orphanResumeText(summary2) {
|
|
@@ -13370,7 +14225,10 @@ var Daemon = class {
|
|
|
13370
14225
|
policy: () => loadConfig().approvalPolicy ?? "commands",
|
|
13371
14226
|
resume: (r, kind) => this.#scheduleResume(r, kind),
|
|
13372
14227
|
log,
|
|
13373
|
-
now: this.#deps.now
|
|
14228
|
+
now: this.#deps.now,
|
|
14229
|
+
qoderScreen: qoderScreen(),
|
|
14230
|
+
// Qoder / Qoder CN 桌面 App 里的确认卡用 App 的真实选项(连接在下面按品牌建好,这里按需取)
|
|
14231
|
+
qoderApp: new QoderAppApprovals({ conn: (tool) => this.#qoderConns.find((c) => c.tool === tool) ?? null, log })
|
|
13374
14232
|
});
|
|
13375
14233
|
this.#workbuddy = new WorkbuddyBridge({
|
|
13376
14234
|
gate: (req, signal) => this.#coord.onGate(req, signal),
|
|
@@ -15309,7 +16167,7 @@ async function runDisable(tools, opts = {}) {
|
|
|
15309
16167
|
|
|
15310
16168
|
// src/commands/install.ts
|
|
15311
16169
|
import { mkdirSync as mkdirSync19, rmSync as rmSync16, writeFileSync as writeFileSync19 } from "fs";
|
|
15312
|
-
import { join as
|
|
16170
|
+
import { join as join29 } from "path";
|
|
15313
16171
|
|
|
15314
16172
|
// src/commands/setup.ts
|
|
15315
16173
|
import qrcode from "qrcode-terminal";
|
|
@@ -15317,9 +16175,9 @@ import qrcode from "qrcode-terminal";
|
|
|
15317
16175
|
// src/commands/remoteCwd.ts
|
|
15318
16176
|
import { existsSync as existsSync14, statSync as statSync14 } from "fs";
|
|
15319
16177
|
import { createInterface } from "readline";
|
|
15320
|
-
import { homedir as
|
|
16178
|
+
import { homedir as homedir8 } from "os";
|
|
15321
16179
|
import { isAbsolute as isAbsolute5, resolve as resolve8 } from "path";
|
|
15322
|
-
function normalizePath(raw, home =
|
|
16180
|
+
function normalizePath(raw, home = homedir8()) {
|
|
15323
16181
|
const s2 = (raw || "").trim();
|
|
15324
16182
|
if (!s2) return null;
|
|
15325
16183
|
const expanded = s2 === "~" ? home : s2.startsWith("~/") || process.platform === "win32" && s2.startsWith("~\\") ? resolve8(home, s2.slice(2)) : s2;
|
|
@@ -15526,7 +16384,7 @@ function inSandbox(env) {
|
|
|
15526
16384
|
}
|
|
15527
16385
|
function precheckWritable(dirs) {
|
|
15528
16386
|
for (const dir3 of dirs) {
|
|
15529
|
-
const probe =
|
|
16387
|
+
const probe = join29(dir3, `.deskbuddy-probe-${process.pid}`);
|
|
15530
16388
|
try {
|
|
15531
16389
|
mkdirSync19(dir3, { recursive: true });
|
|
15532
16390
|
writeFileSync19(probe, "");
|
|
@@ -15936,7 +16794,7 @@ async function runAutoSwitch(opts) {
|
|
|
15936
16794
|
// src/wrap/run.ts
|
|
15937
16795
|
import { randomBytes as randomBytes10 } from "crypto";
|
|
15938
16796
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync21, statSync as statSync16, truncateSync as truncateSync3 } from "fs";
|
|
15939
|
-
import { join as
|
|
16797
|
+
import { join as join32 } from "path";
|
|
15940
16798
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
15941
16799
|
|
|
15942
16800
|
// src/wrap/injector.ts
|
|
@@ -16405,67 +17263,12 @@ function runDirect(real2, args, env = process.env) {
|
|
|
16405
17263
|
});
|
|
16406
17264
|
}
|
|
16407
17265
|
|
|
16408
|
-
// src/wrap/screenRules.ts
|
|
16409
|
-
var Q_RULE_RE = /^(?:─{20,}|-{20,})$/;
|
|
16410
|
-
var Q_TRUST_RE = /Do you trust the files in this folder\?/;
|
|
16411
|
-
var Q_PERMISSION_RE = /Permission Required|Apply this change\?|\d\. Allow once|\d\. Allow for this session/;
|
|
16412
|
-
var Q_LOGIN_RE = /Sign in to continue|Login with QoderCN|How would you like to authenticate/;
|
|
16413
|
-
var Q_BUSY_RE = /\(esc to cancel\b/;
|
|
16414
|
-
var Q_PLACEHOLDER_RE = /^Type your message or @\S*$/;
|
|
16415
|
-
var Q_FOOTER_RE = /· ctx\b/;
|
|
16416
|
-
function bottomRegion(lines3, rules) {
|
|
16417
|
-
const anchor = rules.length >= 3 ? rules[rules.length - 3] : rules[rules.length - 1];
|
|
16418
|
-
return lines3.slice(Math.max(0, anchor - 1)).join("\n");
|
|
16419
|
-
}
|
|
16420
|
-
function qoderCnRule(lines3) {
|
|
16421
|
-
const rules = [];
|
|
16422
|
-
lines3.forEach((l, i) => {
|
|
16423
|
-
if (Q_RULE_RE.test(l.trim())) rules.push(i);
|
|
16424
|
-
});
|
|
16425
|
-
if (rules.length === 0) return { permission: false, input: null };
|
|
16426
|
-
const text = bottomRegion(lines3, rules);
|
|
16427
|
-
if (Q_TRUST_RE.test(text)) return { permission: false, input: null, trust: true };
|
|
16428
|
-
if (Q_LOGIN_RE.test(text)) return { permission: false, input: null, dialog: "login" };
|
|
16429
|
-
const permission = Q_PERMISSION_RE.test(text);
|
|
16430
|
-
let input = null;
|
|
16431
|
-
if (rules.length >= 2) {
|
|
16432
|
-
const a = rules[rules.length - 2];
|
|
16433
|
-
const b = rules[rules.length - 1];
|
|
16434
|
-
const box = lines3.slice(a + 1, b).map((l) => l.trimEnd());
|
|
16435
|
-
if (box.length > 0 && /^\s*>/.test(box[0])) input = box.join("\n").replace(/^\s*>\s?/, "").trim();
|
|
16436
|
-
}
|
|
16437
|
-
const v = { permission, input };
|
|
16438
|
-
if (Q_BUSY_RE.test(text)) v.busy = true;
|
|
16439
|
-
if (input !== null && Q_PLACEHOLDER_RE.test(input)) v.placeholder = true;
|
|
16440
|
-
else if (input !== null && input.startsWith("/")) v.dialog = "slash-menu";
|
|
16441
|
-
if (!v.dialog && lines3.slice(rules[rules.length - 1] + 1).some((l) => l.trim() !== "" && !Q_FOOTER_RE.test(l))) v.dialog = "popup";
|
|
16442
|
-
return v;
|
|
16443
|
-
}
|
|
16444
|
-
function textRejectFor(tool) {
|
|
16445
|
-
if (tool !== "qoder_cn") return void 0;
|
|
16446
|
-
return (text) => /^[/!]/.test(oneLine(text)) ? "slash_command" : null;
|
|
16447
|
-
}
|
|
16448
|
-
var RULES = {
|
|
16449
|
-
claude_code: inspectLines,
|
|
16450
|
-
qoder_cn: qoderCnRule
|
|
16451
|
-
};
|
|
16452
|
-
function screenRuleFor(tool) {
|
|
16453
|
-
return RULES[tool] ?? (() => null);
|
|
16454
|
-
}
|
|
16455
|
-
var INJECT_READY = {
|
|
16456
|
-
claude_code: true,
|
|
16457
|
-
qoder_cn: true
|
|
16458
|
-
};
|
|
16459
|
-
function injectReady(tool) {
|
|
16460
|
-
return INJECT_READY[tool] === true;
|
|
16461
|
-
}
|
|
16462
|
-
|
|
16463
17266
|
// src/wrap/codexLaunch.ts
|
|
16464
17267
|
import { execFileSync as execFileSync7, spawn as spawn6 } from "child_process";
|
|
16465
17268
|
import { closeSync as closeSync10, mkdirSync as mkdirSync20, openSync as openSync10, readdirSync as readdirSync15, readFileSync as readFileSync19, rmSync as rmSync18, statSync as statSync15, truncateSync as truncateSync2, writeFileSync as writeFileSync20, renameSync as renameSync14 } from "fs";
|
|
16466
17269
|
import { request as request2 } from "http";
|
|
16467
17270
|
import { createServer as createServer5 } from "net";
|
|
16468
|
-
import { join as
|
|
17271
|
+
import { join as join30 } from "path";
|
|
16469
17272
|
var TAKEOVER_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "--config", "-m", "--model", "-s", "--sandbox", "-a", "--ask-for-approval", "-C", "--cd", "--enable", "--disable"]);
|
|
16470
17273
|
var SHORT_VALUE_OPTS = /* @__PURE__ */ new Set(["-c", "-m", "-s", "-a", "-C"]);
|
|
16471
17274
|
var TAKEOVER_FLAGS = /* @__PURE__ */ new Set(["--search", "--no-alt-screen", "--dangerously-bypass-approvals-and-sandbox"]);
|
|
@@ -16634,10 +17437,10 @@ async function runCodexWindow(real2, args, d) {
|
|
|
16634
17437
|
}
|
|
16635
17438
|
var MAX_LOG_BYTES2 = 1024 * 1024;
|
|
16636
17439
|
function appServerLogPath() {
|
|
16637
|
-
return
|
|
17440
|
+
return join30(deskbuddyHome(), "codex-app-server.log");
|
|
16638
17441
|
}
|
|
16639
17442
|
function windowsDir2() {
|
|
16640
|
-
return
|
|
17443
|
+
return join30(deskbuddyHome(), "codex-windows");
|
|
16641
17444
|
}
|
|
16642
17445
|
function pidAlive5(pid) {
|
|
16643
17446
|
try {
|
|
@@ -16727,7 +17530,7 @@ function killOwnServer(pid, url, args = freshArgs, kill = (p) => killServer(p))
|
|
|
16727
17530
|
if (a && isAppServerArgs(a, url)) kill(pid);
|
|
16728
17531
|
}
|
|
16729
17532
|
function recordPath(rec) {
|
|
16730
|
-
return
|
|
17533
|
+
return join30(windowsDir2(), `${rec.server}.json`);
|
|
16731
17534
|
}
|
|
16732
17535
|
function writeRecord(rec) {
|
|
16733
17536
|
try {
|
|
@@ -16752,7 +17555,7 @@ function reapWindows(dir3 = windowsDir2(), alive = pidAlive5, args = freshArgs,
|
|
|
16752
17555
|
return;
|
|
16753
17556
|
}
|
|
16754
17557
|
for (const n of names) {
|
|
16755
|
-
const p =
|
|
17558
|
+
const p = join30(dir3, n);
|
|
16756
17559
|
let rec;
|
|
16757
17560
|
try {
|
|
16758
17561
|
rec = JSON.parse(readFileSync19(p, "utf8"));
|
|
@@ -16775,7 +17578,7 @@ function reapWindows(dir3 = windowsDir2(), alive = pidAlive5, args = freshArgs,
|
|
|
16775
17578
|
function launchLog(line) {
|
|
16776
17579
|
try {
|
|
16777
17580
|
mkdirSync20(deskbuddyHome(), { recursive: true });
|
|
16778
|
-
writeFileSync20(
|
|
17581
|
+
writeFileSync20(join30(deskbuddyHome(), "wrap.log"), `${(/* @__PURE__ */ new Date()).toISOString()} [codex ${process.pid}] ${line}
|
|
16779
17582
|
`, { flag: "a" });
|
|
16780
17583
|
} catch {
|
|
16781
17584
|
}
|
|
@@ -16798,9 +17601,9 @@ function defaultCodexWindowDeps(base) {
|
|
|
16798
17601
|
}
|
|
16799
17602
|
|
|
16800
17603
|
// src/wrap/promptLaunch.ts
|
|
16801
|
-
import { randomBytes as randomBytes8, randomUUID as
|
|
16802
|
-
import { homedir as
|
|
16803
|
-
import { join as
|
|
17604
|
+
import { randomBytes as randomBytes8, randomUUID as randomUUID3 } from "crypto";
|
|
17605
|
+
import { homedir as homedir9 } from "os";
|
|
17606
|
+
import { join as join31 } from "path";
|
|
16804
17607
|
var FIRST_LINE_WAIT_MS2 = 2 * 60 * 1e3;
|
|
16805
17608
|
function promptArgs(text) {
|
|
16806
17609
|
return text ? ["--", prepare(text)] : [];
|
|
@@ -16808,7 +17611,7 @@ function promptArgs(text) {
|
|
|
16808
17611
|
function promptEnv(tool, env, launcherPid = process.pid, answerWindowId = "") {
|
|
16809
17612
|
const out = { ...env };
|
|
16810
17613
|
if (tool === "workbuddy") {
|
|
16811
|
-
if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR =
|
|
17614
|
+
if (!out.CODEBUDDY_CONFIG_DIR) out.CODEBUDDY_CONFIG_DIR = join31(homedir9(), ".workbuddy");
|
|
16812
17615
|
out.DESKBUDDY_LAUNCHER_PID = String(launcherPid);
|
|
16813
17616
|
out.DESKBUDDY_LAUNCHED_TOOL = tool;
|
|
16814
17617
|
if (answerWindowId) out.DESKBUDDY_LAUNCHED_WINDOW = answerWindowId;
|
|
@@ -16915,7 +17718,7 @@ async function runPromptLaunch(tool, windowId, cwd, d) {
|
|
|
16915
17718
|
d.log(`[${windowId.slice(0, 8)}] \u6CA1\u7B49\u5230\u9996\u53E5\uFF0C\u8D77\u4E00\u4E2A\u7A7A\u7684 ${tool}`);
|
|
16916
17719
|
}
|
|
16917
17720
|
const answerWindowId = tool === "workbuddy" || tool === "codex" ? randomBytes8(16).toString("hex") : "";
|
|
16918
|
-
const sid = REGISTER_TOOLS.has(tool) ? (d.newSessionId ??
|
|
17721
|
+
const sid = REGISTER_TOOLS.has(tool) ? (d.newSessionId ?? randomUUID3)() : "";
|
|
16919
17722
|
const env = tool === "qoder_cn" ? { ...promptEnv(tool, d.env), ...real2.env ?? {} } : promptEnv(tool, d.env, process.pid, answerWindowId);
|
|
16920
17723
|
const args = [...launchArgs(tool, d.platform, void 0, sid), ...promptArgs(text)];
|
|
16921
17724
|
const early = sid ? startEarlyRegister(tool, sid, cwd, text, d) : null;
|
|
@@ -17073,6 +17876,7 @@ async function applyAnswer(req, io, trust) {
|
|
|
17073
17876
|
const id = req.instructionId;
|
|
17074
17877
|
if (req.expireAt > 0 && Date.now() > req.expireAt) return { instructionId: id, status: "rejected", reason: "expired" };
|
|
17075
17878
|
const a = req.answer;
|
|
17879
|
+
if (a.kind === "qoder-read" || a.kind === "qoder-key") return { instructionId: id, status: "rejected", reason: "unsupported" };
|
|
17076
17880
|
if ((a.kind === "trust" || a.kind === "probe" && a.trust) && !trust) return { instructionId: id, status: "rejected", reason: "unsupported" };
|
|
17077
17881
|
if (a.kind === "probe" && a.login) {
|
|
17078
17882
|
if (!io.loginOnScreen) return { instructionId: id, status: "rejected", reason: "unsupported" };
|
|
@@ -17310,8 +18114,8 @@ async function launchTranscriptReady(tool, sessionId) {
|
|
|
17310
18114
|
return false;
|
|
17311
18115
|
}
|
|
17312
18116
|
}
|
|
17313
|
-
var
|
|
17314
|
-
async function launchRegister(tool, sessionId, cwd, firstLine, final, d =
|
|
18117
|
+
var defaultDeps2 = { qoder: (j) => syncQoderApp(j), workbuddy: (j) => registerWorkbuddySession(j) };
|
|
18118
|
+
async function launchRegister(tool, sessionId, cwd, firstLine, final, d = defaultDeps2) {
|
|
17315
18119
|
if (tool === "workbuddy") return d.workbuddy({ sessionId, cwd, fallbackTitle: firstLine ?? "" });
|
|
17316
18120
|
if (tool !== "qoder_cn") return "skip tool";
|
|
17317
18121
|
const r = await d.qoder({ tool, kind: "register", sessionId, cwd, fallbackTitle: firstLine ?? "" });
|
|
@@ -17339,7 +18143,7 @@ function childEnv(src, windowId, takeover = true) {
|
|
|
17339
18143
|
}
|
|
17340
18144
|
function wrapLog(line) {
|
|
17341
18145
|
try {
|
|
17342
|
-
const p =
|
|
18146
|
+
const p = join32(deskbuddyHome(), "wrap.log");
|
|
17343
18147
|
mkdirSync21(deskbuddyHome(), { recursive: true });
|
|
17344
18148
|
try {
|
|
17345
18149
|
if (statSync16(p).size > MAX_LOG_BYTES3) truncateSync3(p, 0);
|
|
@@ -17643,17 +18447,29 @@ function runAttached(tool, p, term0, windowId, deps2, launched = false, takeover
|
|
|
17643
18447
|
}
|
|
17644
18448
|
};
|
|
17645
18449
|
let holdDepth = 0;
|
|
17646
|
-
|
|
18450
|
+
let holdDrop;
|
|
18451
|
+
const holdKeys = (on, drop) => {
|
|
17647
18452
|
if (on) {
|
|
17648
18453
|
holdDepth++;
|
|
17649
18454
|
held = held ?? [];
|
|
17650
18455
|
return;
|
|
17651
18456
|
}
|
|
18457
|
+
if (drop) holdDrop = drop;
|
|
17652
18458
|
holdDepth = Math.max(0, holdDepth - 1);
|
|
17653
18459
|
if (holdDepth > 0) return;
|
|
17654
18460
|
const h = held ?? [];
|
|
18461
|
+
const d = holdDrop;
|
|
17655
18462
|
held = null;
|
|
17656
|
-
|
|
18463
|
+
holdDrop = void 0;
|
|
18464
|
+
for (const s2 of h) if (!d || !d(s2)) toPty(s2);
|
|
18465
|
+
};
|
|
18466
|
+
const qoderBoxes = takeover && tool === "qoder_cn" ? new QoderBoxTracker() : null;
|
|
18467
|
+
let boxTimer = null;
|
|
18468
|
+
const trackBoxes = () => {
|
|
18469
|
+
try {
|
|
18470
|
+
qoderBoxes?.update(term && !finished ? screenLines(term) : null);
|
|
18471
|
+
} catch {
|
|
18472
|
+
}
|
|
17657
18473
|
};
|
|
17658
18474
|
const injector = new Injector({
|
|
17659
18475
|
now: () => Date.now(),
|
|
@@ -17681,6 +18497,13 @@ function runAttached(tool, p, term0, windowId, deps2, launched = false, takeover
|
|
|
17681
18497
|
}
|
|
17682
18498
|
}
|
|
17683
18499
|
injector.noteOutput();
|
|
18500
|
+
if (qoderBoxes && !boxTimer) {
|
|
18501
|
+
boxTimer = setTimeout(() => {
|
|
18502
|
+
boxTimer = null;
|
|
18503
|
+
trackBoxes();
|
|
18504
|
+
}, 30);
|
|
18505
|
+
boxTimer.unref?.();
|
|
18506
|
+
}
|
|
17684
18507
|
});
|
|
17685
18508
|
const onStdin = (b) => {
|
|
17686
18509
|
const s2 = typeof b === "string" ? b : decoder.write(b);
|
|
@@ -17762,10 +18585,32 @@ function runAttached(tool, p, term0, windowId, deps2, launched = false, takeover
|
|
|
17762
18585
|
}
|
|
17763
18586
|
},
|
|
17764
18587
|
deps2.platform,
|
|
17765
|
-
//
|
|
18588
|
+
// 设备答复收信任框,以及 Qoder CN 授权框的读屏 / 写键(spec 2026-10-09 qodercn-tui-screen-options);
|
|
18589
|
+
// 其余授权框走 hook 的 PermissionRequest,不经这里
|
|
17766
18590
|
(req, reply) => {
|
|
17767
18591
|
reply({ instructionId: req.instructionId, status: "ack" });
|
|
17768
18592
|
const a = req.answer;
|
|
18593
|
+
if (a.kind === "qoder-read" || a.kind === "qoder-key") {
|
|
18594
|
+
if (!qoderBoxes) {
|
|
18595
|
+
reply({ instructionId: req.instructionId, status: "rejected", reason: "unsupported" });
|
|
18596
|
+
return;
|
|
18597
|
+
}
|
|
18598
|
+
const io = {
|
|
18599
|
+
screen: () => term && !finished ? screenLines(term) : null,
|
|
18600
|
+
write: toPty,
|
|
18601
|
+
sleep: (ms) => new Promise((res) => setTimeout(res, ms)),
|
|
18602
|
+
hold: holdKeys,
|
|
18603
|
+
track: qoderBoxes
|
|
18604
|
+
};
|
|
18605
|
+
const run = () => applyQoderAnswer(req, io).then((r) => {
|
|
18606
|
+
const out = r ?? { instructionId: req.instructionId, status: "rejected", reason: "unsupported" };
|
|
18607
|
+
if (a.kind === "qoder-key") deps2.log(`[${windowId.slice(0, 8)}] \u8BBE\u5907\u6388\u6743\u9009\u9879\u7B2C ${a.n} \u9879\u5199\u952E\uFF1A${out.status}${out.reason ? " " + out.reason : ""}`);
|
|
18608
|
+
reply(out);
|
|
18609
|
+
});
|
|
18610
|
+
if (a.kind === "qoder-read") void run().catch(() => reply({ instructionId: req.instructionId, status: "rejected", reason: "no_screen" }));
|
|
18611
|
+
else answerChain = answerChain.then(run).catch(() => reply({ instructionId: req.instructionId, status: "failed", mode: "window", reason: "partial" }));
|
|
18612
|
+
return;
|
|
18613
|
+
}
|
|
17769
18614
|
if (trust && (a.kind === "trust" || a.kind === "probe" && a.trust)) trust.noteContact(a.prompt ?? "trust");
|
|
17770
18615
|
if (!trustCtx || !(a.kind === "trust" || a.kind === "probe" && a.trust)) {
|
|
17771
18616
|
reply({ instructionId: req.instructionId, status: "rejected", reason: "unsupported" });
|
|
@@ -17799,6 +18644,7 @@ function runAttached(tool, p, term0, windowId, deps2, launched = false, takeover
|
|
|
17799
18644
|
if (takeover) timer = setInterval(() => {
|
|
17800
18645
|
injector.tick().catch(() => {
|
|
17801
18646
|
});
|
|
18647
|
+
trackBoxes();
|
|
17802
18648
|
try {
|
|
17803
18649
|
withdraw?.tick();
|
|
17804
18650
|
} catch {
|