wezard 0.3.3
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/.claude-plugin/marketplace.json +14 -0
- package/.claude-plugin/plugin.json +14 -0
- package/CLAUDE.md +83 -0
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/cli/weclaude.sh +236 -0
- package/commands/audit.md +7 -0
- package/commands/wrc.md +7 -0
- package/config.example.jsonc +102 -0
- package/dist/cli/audit.js +156 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/init.js +311 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/sync.js +175 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/daemon/approval.js +1301 -0
- package/dist/daemon/approval.js.map +1 -0
- package/dist/daemon/ask.js +101 -0
- package/dist/daemon/ask.js.map +1 -0
- package/dist/daemon/audit.js +168 -0
- package/dist/daemon/audit.js.map +1 -0
- package/dist/daemon/cc-bridge.js +204 -0
- package/dist/daemon/cc-bridge.js.map +1 -0
- package/dist/daemon/cfg-sync.js +240 -0
- package/dist/daemon/cfg-sync.js.map +1 -0
- package/dist/daemon/claim.js +95 -0
- package/dist/daemon/claim.js.map +1 -0
- package/dist/daemon/detail.js +153 -0
- package/dist/daemon/detail.js.map +1 -0
- package/dist/daemon/graph.js +176 -0
- package/dist/daemon/graph.js.map +1 -0
- package/dist/daemon/http.js +83 -0
- package/dist/daemon/http.js.map +1 -0
- package/dist/daemon/inbound.js +919 -0
- package/dist/daemon/inbound.js.map +1 -0
- package/dist/daemon/index.js +569 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/last-response.js +59 -0
- package/dist/daemon/last-response.js.map +1 -0
- package/dist/daemon/mirror-bridge.js +3512 -0
- package/dist/daemon/mirror-bridge.js.map +1 -0
- package/dist/daemon/mirror-store.js +39 -0
- package/dist/daemon/mirror-store.js.map +1 -0
- package/dist/daemon/outbound.js +42 -0
- package/dist/daemon/outbound.js.map +1 -0
- package/dist/daemon/peers.js +146 -0
- package/dist/daemon/peers.js.map +1 -0
- package/dist/daemon/pending.js +90 -0
- package/dist/daemon/pending.js.map +1 -0
- package/dist/daemon/quota.js +164 -0
- package/dist/daemon/quota.js.map +1 -0
- package/dist/daemon/redact.js +27 -0
- package/dist/daemon/redact.js.map +1 -0
- package/dist/daemon/session-cache.js +149 -0
- package/dist/daemon/session-cache.js.map +1 -0
- package/dist/daemon/session-scan.js +273 -0
- package/dist/daemon/session-scan.js.map +1 -0
- package/dist/daemon/sessions.js +35 -0
- package/dist/daemon/sessions.js.map +1 -0
- package/dist/daemon/spawn-tmux.js +213 -0
- package/dist/daemon/spawn-tmux.js.map +1 -0
- package/dist/daemon/tips.js +25 -0
- package/dist/daemon/tips.js.map +1 -0
- package/dist/daemon/topics.js +197 -0
- package/dist/daemon/topics.js.map +1 -0
- package/dist/daemon/usage.js +358 -0
- package/dist/daemon/usage.js.map +1 -0
- package/dist/daemon/wedoc.js +210 -0
- package/dist/daemon/wedoc.js.map +1 -0
- package/dist/daemon/ws.js +67 -0
- package/dist/daemon/ws.js.map +1 -0
- package/dist/mcp/server.js +385 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/shared/ansi.js +134 -0
- package/dist/shared/ansi.js.map +1 -0
- package/dist/shared/chat-http.js +195 -0
- package/dist/shared/chat-http.js.map +1 -0
- package/dist/shared/chat-render.js +53 -0
- package/dist/shared/chat-render.js.map +1 -0
- package/dist/shared/chat-view.js +116 -0
- package/dist/shared/chat-view.js.map +1 -0
- package/dist/shared/cli-backends.js +304 -0
- package/dist/shared/cli-backends.js.map +1 -0
- package/dist/shared/config-writer.js +39 -0
- package/dist/shared/config-writer.js.map +1 -0
- package/dist/shared/config.js +278 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/detail-render.js +836 -0
- package/dist/shared/detail-render.js.map +1 -0
- package/dist/shared/detail-store.js +205 -0
- package/dist/shared/detail-store.js.map +1 -0
- package/dist/shared/highlight.js +154 -0
- package/dist/shared/highlight.js.map +1 -0
- package/dist/shared/lan-ip.js +35 -0
- package/dist/shared/lan-ip.js.map +1 -0
- package/dist/shared/log.js +18 -0
- package/dist/shared/log.js.map +1 -0
- package/dist/shared/md-chunk.js +99 -0
- package/dist/shared/md-chunk.js.map +1 -0
- package/dist/shared/paths.js +12 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/session-label.js +67 -0
- package/dist/shared/session-label.js.map +1 -0
- package/dist/shared/web-assets.js +51 -0
- package/dist/shared/web-assets.js.map +1 -0
- package/dist/svr/index.js +246 -0
- package/dist/svr/index.js.map +1 -0
- package/hooks/hooks.json +16 -0
- package/hooks/pre-tool-use.sh +167 -0
- package/launchd/com.weclaude.daemon.plist.template +45 -0
- package/package.json +97 -0
- package/scripts/install.sh +73 -0
- package/scripts/postinstall.sh +12 -0
- package/scripts/uninstall.sh +44 -0
- package/systemd/weclaude.service.template +17 -0
- package/web/chat.css +171 -0
- package/web/chat.js +367 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Pending request store: req_id → resolver. In-memory, daemon-lifetime.
|
|
2
|
+
// Used for both PreToolUse approvals and any other "send card → wait click" flows.
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { baseOfKey } from "../shared/session-label.js";
|
|
5
|
+
const store = new Map();
|
|
6
|
+
export const createPending = ({ meta, timeoutMs }) => {
|
|
7
|
+
const reqId = randomUUID();
|
|
8
|
+
const promise = new Promise((resolve, reject) => {
|
|
9
|
+
const timer = setTimeout(() => {
|
|
10
|
+
store.delete(reqId);
|
|
11
|
+
reject(new Error("pending_timeout"));
|
|
12
|
+
}, timeoutMs);
|
|
13
|
+
store.set(reqId, { meta, resolve, reject, timer });
|
|
14
|
+
});
|
|
15
|
+
return { reqId, promise };
|
|
16
|
+
};
|
|
17
|
+
export const resolvePending = (reqId, decision) => {
|
|
18
|
+
const p = store.get(reqId);
|
|
19
|
+
if (!p)
|
|
20
|
+
return false;
|
|
21
|
+
clearTimeout(p.timer);
|
|
22
|
+
store.delete(reqId);
|
|
23
|
+
p.resolve(decision);
|
|
24
|
+
return true;
|
|
25
|
+
};
|
|
26
|
+
// Used by batch send-failure: kicks each member's awaiting handler into its
|
|
27
|
+
// catch path so it can return the configured fallbackOnError (incl. "ask").
|
|
28
|
+
export const failPending = (reqId, err) => {
|
|
29
|
+
const p = store.get(reqId);
|
|
30
|
+
if (!p)
|
|
31
|
+
return false;
|
|
32
|
+
clearTimeout(p.timer);
|
|
33
|
+
store.delete(reqId);
|
|
34
|
+
p.reject(err);
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
export const getPending = (reqId) => store.get(reqId)?.meta;
|
|
38
|
+
export const listPending = () => Array.from(store.entries()).map(([reqId, p]) => ({ reqId, meta: p.meta }));
|
|
39
|
+
const resolvedStash = new Map();
|
|
40
|
+
const RESOLVED_TTL_MS = 5 * 60_000;
|
|
41
|
+
const RESOLVED_MAX = 200;
|
|
42
|
+
const gcResolved = () => {
|
|
43
|
+
const cutoff = Date.now() - RESOLVED_TTL_MS;
|
|
44
|
+
for (const [k, v] of resolvedStash) {
|
|
45
|
+
if (v.resolvedAt < cutoff)
|
|
46
|
+
resolvedStash.delete(k);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const stashResolved = (reqId, meta, decision) => {
|
|
50
|
+
resolvedStash.set(reqId, { meta, decision, resolvedAt: Date.now() });
|
|
51
|
+
if (resolvedStash.size > RESOLVED_MAX)
|
|
52
|
+
gcResolved();
|
|
53
|
+
};
|
|
54
|
+
export const getResolvedSnapshot = (reqId) => {
|
|
55
|
+
const e = resolvedStash.get(reqId);
|
|
56
|
+
if (!e)
|
|
57
|
+
return undefined;
|
|
58
|
+
if (Date.now() - e.resolvedAt > RESOLVED_TTL_MS) {
|
|
59
|
+
resolvedStash.delete(reqId);
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return { meta: e.meta, decision: e.decision };
|
|
63
|
+
};
|
|
64
|
+
// 批量 resolve 同 chat 下仍在长轮询的 approval 请求。
|
|
65
|
+
// 用途: 用户点 allow_window 时,同一个 WeCom 聊天里其它 pending 卡 (可能来自
|
|
66
|
+
// 别的 session) 都应一并放行 — 窗口按 chat 维度生效, 别的 session 反正也会
|
|
67
|
+
// 在下次请求时被 isAutoWindowActive 短路, 提前扫掉纯粹是省用户手指。返回被
|
|
68
|
+
// 放行的 (reqId, meta) 列表, 供调用方做提示展示 (resolve 后 store 里就拿不
|
|
69
|
+
// 到 meta 了)。
|
|
70
|
+
export const resolvePendingsByChat = (chatKey, decision, excludeReqId) => {
|
|
71
|
+
// 按 base principal 比对: chatKey 是带 `#tag` 的 session key, 但窗口是 chat 级
|
|
72
|
+
// 授权 — 同 chat 下其它 tag 会话的待批卡也在这一次点击的覆盖范围内。
|
|
73
|
+
const base = baseOfKey(chatKey);
|
|
74
|
+
const hits = [];
|
|
75
|
+
for (const [reqId, p] of store.entries()) {
|
|
76
|
+
if (reqId === excludeReqId)
|
|
77
|
+
continue;
|
|
78
|
+
if (p.meta.kind !== "approval")
|
|
79
|
+
continue;
|
|
80
|
+
if (!p.meta.chatKey || baseOfKey(p.meta.chatKey) !== base)
|
|
81
|
+
continue;
|
|
82
|
+
hits.push({ reqId, meta: p.meta });
|
|
83
|
+
}
|
|
84
|
+
hits.forEach(({ reqId, meta }) => {
|
|
85
|
+
stashResolved(reqId, meta, decision);
|
|
86
|
+
resolvePending(reqId, decision);
|
|
87
|
+
});
|
|
88
|
+
return hits;
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=pending.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending.js","sourceRoot":"","sources":["../../daemon/pending.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { expandHome } from "../shared/paths.js";
|
|
2
|
+
import { runTmux, trustWorkspace } from "./spawn-tmux.js";
|
|
3
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4
|
+
const stripAnsi = (s) =>
|
|
5
|
+
// eslint-disable-next-line no-control-regex
|
|
6
|
+
s.replace(/\[[0-9;?]*[A-Za-z]/g, "");
|
|
7
|
+
// Parse the `/usage` panel (Usage tab). Layout, per Claude Code v2.1.x:
|
|
8
|
+
// Current session
|
|
9
|
+
// ██████… 22% used
|
|
10
|
+
// Resets 11:10pm (Asia/Shanghai)
|
|
11
|
+
// Current week (all models)
|
|
12
|
+
// ██████… 82% used
|
|
13
|
+
// Resets Jul 10 at 2pm (Asia/Shanghai)
|
|
14
|
+
// What's contributing to your limits usage?
|
|
15
|
+
// 62% of your usage was at >150k context ← factor headlines
|
|
16
|
+
// Section headers switch the bucket; "% used" / "Resets" lines fill it.
|
|
17
|
+
export const parseUsagePanel = (text) => {
|
|
18
|
+
const limits = [];
|
|
19
|
+
const factors = [];
|
|
20
|
+
const header = /^Current\s+(.+?)\s*$/i; // "Current session", "Current week (all models)"
|
|
21
|
+
const pctUsed = /(\d+)%\s*used/i;
|
|
22
|
+
const resets = /Resets\s+(.+?)\s*$/i;
|
|
23
|
+
const factor = /^\s*(\d+)%\s+of your usage\b(.*)$/i;
|
|
24
|
+
let cur;
|
|
25
|
+
const close = () => { if (cur && Number.isFinite(cur.pct))
|
|
26
|
+
limits.push(cur); cur = undefined; };
|
|
27
|
+
for (const raw of stripAnsi(text).split("\n")) {
|
|
28
|
+
const line = raw.trim();
|
|
29
|
+
// A factor headline ("N% of your usage …") also ends any open limit block.
|
|
30
|
+
const fm = factor.exec(raw);
|
|
31
|
+
if (fm) {
|
|
32
|
+
close();
|
|
33
|
+
factors.push(`${fm[1]}%${fm[2]}`.replace(/\s+/g, " ").trim());
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// A "Current …" line with no % is a new limit header (generic over
|
|
37
|
+
// session / week (all models) / week (<model>) / any future bucket).
|
|
38
|
+
const hm = header.exec(line);
|
|
39
|
+
if (hm && !pctUsed.test(line) && !/resets/i.test(line)) {
|
|
40
|
+
close();
|
|
41
|
+
cur = { label: hm[1] ?? "", pct: NaN };
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!cur)
|
|
45
|
+
continue;
|
|
46
|
+
const pm = pctUsed.exec(line);
|
|
47
|
+
if (pm && !Number.isFinite(cur.pct)) {
|
|
48
|
+
cur.pct = Number(pm[1]);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const rm = resets.exec(line);
|
|
52
|
+
if (rm && cur.resets == null)
|
|
53
|
+
cur.resets = rm[1];
|
|
54
|
+
}
|
|
55
|
+
close();
|
|
56
|
+
return { limits, factors };
|
|
57
|
+
};
|
|
58
|
+
// Poll `fn` every `stepMs` until it resolves truthy or `budgetMs` elapses.
|
|
59
|
+
const pollUntil = async (fn, budgetMs, stepMs) => {
|
|
60
|
+
const deadline = Date.now() + budgetMs;
|
|
61
|
+
for (;;) {
|
|
62
|
+
const v = await fn();
|
|
63
|
+
if (v)
|
|
64
|
+
return v;
|
|
65
|
+
if (Date.now() >= deadline)
|
|
66
|
+
return undefined;
|
|
67
|
+
await sleep(stepMs);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const capture = async (pane, rows) => {
|
|
71
|
+
const r = await runTmux(["capture-pane", "-t", pane, "-p", "-S", `-${rows}`]);
|
|
72
|
+
return r.ok ? r.stdout : "";
|
|
73
|
+
};
|
|
74
|
+
// Drive `/usage` in a throwaway pane and return the parsed panel. Best-effort
|
|
75
|
+
// teardown in `finally` so a dead session name never lingers.
|
|
76
|
+
export const captureQuota = async (cfg, log) => {
|
|
77
|
+
const cwd = expandHome(cfg.wrc.cwd);
|
|
78
|
+
const session = `${cfg.wrc.tmuxPrefix}-usage`; // isolated from the mirror session
|
|
79
|
+
const claudeBin = cfg.wrc.claudeBin;
|
|
80
|
+
const probe = await runTmux(["-V"]);
|
|
81
|
+
if (!probe.ok)
|
|
82
|
+
throw new Error(`tmux not available: ${probe.stderr.trim() || probe.code}`);
|
|
83
|
+
trustWorkspace(claudeBin, cwd, log); // skip the "trust this folder" prompt
|
|
84
|
+
await runTmux(["kill-session", "-t", session]); // clear any stale probe pane
|
|
85
|
+
const paneEnv = ["-e", "DISABLE_AUTO_UPDATE=true", "-e", "DISABLE_UPDATE_PROMPT=true"];
|
|
86
|
+
const created = await runTmux(["new-session", "-d", "-s", session, "-x", "200", "-y", "50", "-c", cwd, ...paneEnv, "-P", "-F", "#{pane_id}"]);
|
|
87
|
+
if (!created.ok)
|
|
88
|
+
throw new Error(`tmux new-session failed: ${created.stderr.trim() || created.code}`);
|
|
89
|
+
const pane = created.stdout.split("\n").map((s) => s.trim()).filter(Boolean)[0] ?? "";
|
|
90
|
+
if (!pane)
|
|
91
|
+
throw new Error("tmux returned no pane id");
|
|
92
|
+
try {
|
|
93
|
+
await runTmux(["send-keys", "-t", pane, `DISABLE_AUTOUPDATER=1 ${claudeBin}`, "Enter"]);
|
|
94
|
+
// Wait for the TUI to reach an interactive prompt. Clear a stray trust
|
|
95
|
+
// dialog defensively (pre-trust usually handles it, but be robust).
|
|
96
|
+
const ready = await pollUntil(async () => {
|
|
97
|
+
const cap = await capture(pane, 60);
|
|
98
|
+
if (/trust this folder|Yes, I trust/i.test(cap)) {
|
|
99
|
+
await runTmux(["send-keys", "-t", pane, "1"]);
|
|
100
|
+
await sleep(300);
|
|
101
|
+
await runTmux(["send-keys", "-t", pane, "Enter"]);
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return /for shortcuts|auto mode on|Try "/i.test(cap) ? true : undefined;
|
|
105
|
+
}, 30_000, 500);
|
|
106
|
+
if (!ready)
|
|
107
|
+
throw new Error("claude TUI did not reach interactive prompt in 30s");
|
|
108
|
+
await sleep(1200); // let the input box settle before the slash command
|
|
109
|
+
await runTmux(["send-keys", "-t", pane, "/usage"]);
|
|
110
|
+
await sleep(1000); // let the slash-command menu resolve, then run it
|
|
111
|
+
await runTmux(["send-keys", "-t", pane, "Enter"]);
|
|
112
|
+
// Wait for the panel to render the weekly block.
|
|
113
|
+
const panelText = await pollUntil(async () => {
|
|
114
|
+
const cap = await capture(pane, 180);
|
|
115
|
+
return /Current week/i.test(cap) && /%\s*used/i.test(cap) ? cap : undefined;
|
|
116
|
+
}, 20_000, 500);
|
|
117
|
+
if (!panelText)
|
|
118
|
+
throw new Error("/usage panel did not render in 20s");
|
|
119
|
+
await sleep(800); // let percentages finish painting
|
|
120
|
+
const finalText = await capture(pane, 180);
|
|
121
|
+
const report = parseUsagePanel(finalText || panelText);
|
|
122
|
+
report.raw = stripAnsi(finalText || panelText);
|
|
123
|
+
return report;
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
await runTmux(["send-keys", "-t", pane, "Escape"]).catch(() => undefined);
|
|
127
|
+
await runTmux(["kill-session", "-t", session]).catch(() => undefined);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const bar20 = (pct) => {
|
|
131
|
+
const n = Math.max(0, Math.min(20, Math.round((pct / 100) * 20)));
|
|
132
|
+
return "█".repeat(n) + "░".repeat(20 - n);
|
|
133
|
+
};
|
|
134
|
+
// Map the raw panel label to a WeCom heading. Generic: "session" → 5h window,
|
|
135
|
+
// "week (X)" → 本周·X, "week" → 本周, anything else passes through verbatim so a
|
|
136
|
+
// future limit bucket still renders.
|
|
137
|
+
const headingFor = (label) => {
|
|
138
|
+
if (/^session\b/i.test(label))
|
|
139
|
+
return "⏰ 当前 5h 窗口";
|
|
140
|
+
const wk = /^week\s*(?:\(([^)]*)\))?/i.exec(label);
|
|
141
|
+
if (wk)
|
|
142
|
+
return wk[1] ? `🗓️ 本周 · ${wk[1]}` : "🗓️ 本周";
|
|
143
|
+
return `📊 ${label}`;
|
|
144
|
+
};
|
|
145
|
+
export const renderQuotaReport = (r) => {
|
|
146
|
+
const out = ["[weclaude] 📊 真实额度 · Claude Code /usage"];
|
|
147
|
+
for (const l of r.limits) {
|
|
148
|
+
out.push("", headingFor(l.label), ` ${bar20(l.pct)} **${l.pct}%**`);
|
|
149
|
+
if (l.resets)
|
|
150
|
+
out.push(` 重置: ${l.resets}`);
|
|
151
|
+
}
|
|
152
|
+
if (r.limits.length === 0) {
|
|
153
|
+
out.push("", "⚠️ 未能从 /usage 面板读到额度数字");
|
|
154
|
+
if (r.raw)
|
|
155
|
+
out.push("```", r.raw.split("\n").filter((l) => l.trim()).slice(0, 12).join("\n"), "```");
|
|
156
|
+
}
|
|
157
|
+
if (r.factors.length) {
|
|
158
|
+
out.push("", "🔎 额度构成 (近24h · 本机本地会话估算)");
|
|
159
|
+
for (const f of r.factors.slice(0, 6))
|
|
160
|
+
out.push(` • ${f}`);
|
|
161
|
+
}
|
|
162
|
+
return out.join("\n");
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=quota.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota.js","sourceRoot":"","sources":["../../daemon/quota.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Sensitive value redaction for tool_input before sending to IM.
|
|
2
|
+
// Keep it boring: regex on JSON keys/values that look like credentials.
|
|
3
|
+
const KEY_RE = /(password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|authorization|cookie)/i;
|
|
4
|
+
const VALUE_RE = /^(eyJ[A-Za-z0-9_-]{10,}|sk-[A-Za-z0-9]{16,}|[A-Fa-f0-9]{32,}|ghp_[A-Za-z0-9]{20,})$/;
|
|
5
|
+
const redactString = (s) => (VALUE_RE.test(s) ? "***" : s);
|
|
6
|
+
export const redact = (input) => {
|
|
7
|
+
if (input === null || input === undefined)
|
|
8
|
+
return input;
|
|
9
|
+
if (typeof input === "string")
|
|
10
|
+
return redactString(input);
|
|
11
|
+
if (Array.isArray(input))
|
|
12
|
+
return input.map(redact);
|
|
13
|
+
if (typeof input === "object") {
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const [k, v] of Object.entries(input)) {
|
|
16
|
+
if (KEY_RE.test(k) && typeof v === "string") {
|
|
17
|
+
out[k] = "***";
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
out[k] = redact(v);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
return input;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../daemon/redact.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,wEAAwE;AACxE,MAAM,MAAM,GAAG,qFAAqF,CAAC;AACrG,MAAM,QAAQ,GAAG,qFAAqF,CAAC;AAEvG,MAAM,YAAY,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3E,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAc,EAAW,EAAE;IAChD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YACtE,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC5C,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Session cache for "Allow for session" decisions.
|
|
2
|
+
// Key = (sessionId, toolName, hash(cmd_prefix)). In-memory, TTL via approval.sessionCacheMinutes.
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { expandHome } from "../shared/paths.js";
|
|
7
|
+
import { baseOfKey } from "../shared/session-label.js";
|
|
8
|
+
const cache = new Map();
|
|
9
|
+
const norm = (s) => s.trim().replace(/\s+/g, " ");
|
|
10
|
+
const cmdPrefix = (toolName, toolInput) => {
|
|
11
|
+
// Bash: first token of command. Others: stable JSON.
|
|
12
|
+
if (toolName === "Bash" && toolInput && typeof toolInput === "object") {
|
|
13
|
+
const cmd = toolInput.command;
|
|
14
|
+
if (typeof cmd === "string") {
|
|
15
|
+
const first = norm(cmd).split(" ")[0] ?? "";
|
|
16
|
+
return first;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(toolInput);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return String(toolInput);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
export const cacheKey = (sessionId, toolName, toolInput) => {
|
|
27
|
+
const h = createHash("sha1").update(cmdPrefix(toolName, toolInput)).digest("hex").slice(0, 12);
|
|
28
|
+
return `${sessionId}::${toolName}::${h}`;
|
|
29
|
+
};
|
|
30
|
+
export const cacheGet = (key) => {
|
|
31
|
+
const e = cache.get(key);
|
|
32
|
+
if (!e)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (Date.now() > e.expiresAt) {
|
|
35
|
+
cache.delete(key);
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return e.decision;
|
|
39
|
+
};
|
|
40
|
+
export const cachePut = (key, decision, ttlMs) => {
|
|
41
|
+
cache.set(key, { decision, expiresAt: Date.now() + ttlMs });
|
|
42
|
+
};
|
|
43
|
+
export const cacheClear = () => cache.clear();
|
|
44
|
+
// ── Per-chat auto-approve window ───────────────────────────────────────
|
|
45
|
+
// Set by `allow_window` clicks; while active, approval requests routed to
|
|
46
|
+
// the SAME chat (WeCom principal like "user:abc" / "chat:wc...") bypass the
|
|
47
|
+
// card flow and return allow immediately. Chat-scoped instead of session-scoped
|
|
48
|
+
// so that multiple concurrent sessions bound to the same WeCom chat share the
|
|
49
|
+
// user's "放行 N 分钟" choice — a new session started under the same chat
|
|
50
|
+
// inherits the window until it expires.
|
|
51
|
+
// Persisted to disk so daemon restart doesn't drop the user's choice.
|
|
52
|
+
const autoApproveUntilByChat = new Map();
|
|
53
|
+
// 归一化: 审批目标是 mirror target (`user:abc#fix`), 带 `#tag`。窗口是 CHAT 级
|
|
54
|
+
// 的授权 — 用户点一次「放行 N 分钟」是对这个聊天说的, 不是对某个 tag 会话说的,
|
|
55
|
+
// 所以所有窗口读写都先塌到 base principal, 同 chat 下新开的 `#tag` 会话直接继承。
|
|
56
|
+
// 调用方仍传带 tag 的 key (卡片渲染要 tag emoji), 归一化只发生在存储边界。
|
|
57
|
+
const scopeOf = baseOfKey;
|
|
58
|
+
const windowMetaByChat = new Map();
|
|
59
|
+
// ── Persistence ─────────────────────────────────────────────────────────
|
|
60
|
+
let persistPath;
|
|
61
|
+
const writeAtomic = (path, text) => {
|
|
62
|
+
const tmp = `${path}.tmp`;
|
|
63
|
+
writeFileSync(tmp, text, "utf8");
|
|
64
|
+
renameSync(tmp, path);
|
|
65
|
+
};
|
|
66
|
+
const persist = () => {
|
|
67
|
+
if (!persistPath)
|
|
68
|
+
return;
|
|
69
|
+
const windows = {};
|
|
70
|
+
for (const [chatKey, until] of autoApproveUntilByChat.entries()) {
|
|
71
|
+
windows[chatKey] = { until, meta: windowMetaByChat.get(chatKey) };
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
writeAtomic(persistPath, JSON.stringify({ windows }));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* best-effort; in-memory state still authoritative */
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/** Call once at daemon startup. Loads any unexpired windows from disk. */
|
|
81
|
+
export const initAutoWindowPersistence = (stateDir) => {
|
|
82
|
+
const dir = expandHome(stateDir);
|
|
83
|
+
// 文件名改成 -by-chat 版本; 旧的 auto-windows.json (sessionId 键) 直接搁置,
|
|
84
|
+
// 不迁移 — 那些键在新语义下永远不会匹配, 靠自然 TTL 淘汰即可。
|
|
85
|
+
persistPath = join(dir, "auto-windows-by-chat.json");
|
|
86
|
+
try {
|
|
87
|
+
mkdirSync(dirname(persistPath), { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* ignore */
|
|
91
|
+
}
|
|
92
|
+
if (!existsSync(persistPath))
|
|
93
|
+
return;
|
|
94
|
+
try {
|
|
95
|
+
const data = JSON.parse(readFileSync(persistPath, "utf8"));
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
for (const [chatKey, entry] of Object.entries(data.windows ?? {})) {
|
|
98
|
+
if (!entry || typeof entry.until !== "number")
|
|
99
|
+
continue;
|
|
100
|
+
if (entry.until <= now)
|
|
101
|
+
continue;
|
|
102
|
+
// 旧盘上可能是带 tag 的 key — 落地时一并塌到 base。
|
|
103
|
+
const k = scopeOf(chatKey);
|
|
104
|
+
autoApproveUntilByChat.set(k, Math.max(autoApproveUntilByChat.get(k) ?? 0, entry.until));
|
|
105
|
+
if (entry.meta)
|
|
106
|
+
windowMetaByChat.set(k, entry.meta);
|
|
107
|
+
}
|
|
108
|
+
// 清掉过期项后落盘一次。
|
|
109
|
+
persist();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* corrupt file — start fresh */
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
export const setAutoWindow = (chatKey, ttlMs, meta) => {
|
|
116
|
+
const until = Date.now() + ttlMs;
|
|
117
|
+
autoApproveUntilByChat.set(scopeOf(chatKey), until);
|
|
118
|
+
if (meta)
|
|
119
|
+
windowMetaByChat.set(scopeOf(chatKey), meta);
|
|
120
|
+
persist();
|
|
121
|
+
return until;
|
|
122
|
+
};
|
|
123
|
+
export const getWindowMeta = (chatKey) => windowMetaByChat.get(scopeOf(chatKey));
|
|
124
|
+
export const autoWindowRemainingMs = (chatKey) => Math.max(0, (autoApproveUntilByChat.get(scopeOf(chatKey)) ?? 0) - Date.now());
|
|
125
|
+
export const isAutoWindowActive = (chatKey) => {
|
|
126
|
+
const base = scopeOf(chatKey);
|
|
127
|
+
const until = autoApproveUntilByChat.get(base);
|
|
128
|
+
if (until === undefined)
|
|
129
|
+
return false;
|
|
130
|
+
if (Date.now() >= until) {
|
|
131
|
+
autoApproveUntilByChat.delete(base);
|
|
132
|
+
windowMetaByChat.delete(base);
|
|
133
|
+
persist();
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
};
|
|
138
|
+
export const clearAutoWindow = (chatKey) => {
|
|
139
|
+
if (chatKey === undefined) {
|
|
140
|
+
autoApproveUntilByChat.clear();
|
|
141
|
+
windowMetaByChat.clear();
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
autoApproveUntilByChat.delete(scopeOf(chatKey));
|
|
145
|
+
windowMetaByChat.delete(scopeOf(chatKey));
|
|
146
|
+
}
|
|
147
|
+
persist();
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=session-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-cache.js","sourceRoot":"","sources":["../../daemon/session-cache.ts"],"names":[],"mappings":"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"}
|