@letrquan/book 0.2.0 → 0.4.0-beta.0
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/CHANGELOG.md +3771 -40
- package/README.md +135 -1311
- package/dist/additional-roots-VAWRQ625.js +32 -0
- package/dist/{app-MOXXWEOY.js → app-J7BVPSUH.js} +9648 -5509
- package/dist/app-J7BVPSUH.js.map +1 -0
- package/dist/chunk-2AAOF5B5.js +28 -0
- package/dist/chunk-2AAOF5B5.js.map +1 -0
- package/dist/chunk-32WBCUAN.js +660 -0
- package/dist/chunk-32WBCUAN.js.map +1 -0
- package/dist/chunk-4DKHLZRI.js +120 -0
- package/dist/chunk-4DKHLZRI.js.map +1 -0
- package/dist/{chunk-CAGOB2N4.js → chunk-62EEAUID.js} +6 -4
- package/dist/chunk-62EEAUID.js.map +1 -0
- package/dist/chunk-AHC7OU75.js +1124 -0
- package/dist/chunk-AHC7OU75.js.map +1 -0
- package/dist/{chunk-E75IPST5.js → chunk-AJZWLKLQ.js} +73 -70
- package/dist/chunk-AJZWLKLQ.js.map +1 -0
- package/dist/chunk-B5FC3MYZ.js +395 -0
- package/dist/chunk-B5FC3MYZ.js.map +1 -0
- package/dist/{chunk-KWHQ2XXL.js → chunk-DNFQTSMJ.js} +55 -13
- package/dist/chunk-DNFQTSMJ.js.map +1 -0
- package/dist/{chunk-J7MIZ5CA.js → chunk-E5WLQSKA.js} +719 -89
- package/dist/chunk-E5WLQSKA.js.map +1 -0
- package/dist/chunk-ECLC4CIJ.js +1181 -0
- package/dist/chunk-ECLC4CIJ.js.map +1 -0
- package/dist/chunk-EEX6QNXV.js +127 -0
- package/dist/chunk-EEX6QNXV.js.map +1 -0
- package/dist/chunk-EFXVSQ5J.js +931 -0
- package/dist/chunk-EFXVSQ5J.js.map +1 -0
- package/dist/{chunk-V4BOYR52.js → chunk-ENLB7NYV.js} +1304 -326
- package/dist/chunk-ENLB7NYV.js.map +1 -0
- package/dist/chunk-FPR7SKIE.js +6926 -0
- package/dist/chunk-FPR7SKIE.js.map +1 -0
- package/dist/{chunk-DGMQEI6Y.js → chunk-HFB74CLM.js} +30 -9
- package/dist/chunk-HFB74CLM.js.map +1 -0
- package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
- package/dist/chunk-HOZXKOE3.js.map +1 -0
- package/dist/chunk-IDYWN37T.js +36 -0
- package/dist/chunk-IDYWN37T.js.map +1 -0
- package/dist/chunk-J4F2QLM2.js +273 -0
- package/dist/chunk-J4F2QLM2.js.map +1 -0
- package/dist/chunk-JGF5HLSI.js +1065 -0
- package/dist/chunk-JGF5HLSI.js.map +1 -0
- package/dist/{chunk-XIFFTATU.js → chunk-KDNWMKXV.js} +7166 -4237
- package/dist/chunk-KDNWMKXV.js.map +1 -0
- package/dist/chunk-KXAYUIKP.js +90 -0
- package/dist/chunk-KXAYUIKP.js.map +1 -0
- package/dist/{chunk-3U5IBM24.js → chunk-MEOB2DZA.js} +12 -2
- package/dist/chunk-MEOB2DZA.js.map +1 -0
- package/dist/{chunk-WKBO5R4O.js → chunk-MV5PQNIA.js} +2 -2
- package/dist/chunk-MV5PQNIA.js.map +1 -0
- package/dist/chunk-N2G4Q7ME.js +2672 -0
- package/dist/chunk-N2G4Q7ME.js.map +1 -0
- package/dist/chunk-OJBMDM5H.js +299 -0
- package/dist/chunk-OJBMDM5H.js.map +1 -0
- package/dist/{chunk-URLQAXTR.js → chunk-PTDC7ZUP.js} +2 -2
- package/dist/chunk-PTDC7ZUP.js.map +1 -0
- package/dist/{chunk-574C6MQR.js → chunk-REASC2QG.js} +173 -64
- package/dist/chunk-REASC2QG.js.map +1 -0
- package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
- package/dist/chunk-RT5SATF7.js.map +1 -0
- package/dist/chunk-SBTU5BEC.js +114 -0
- package/dist/chunk-SBTU5BEC.js.map +1 -0
- package/dist/{chunk-5KLVH3PY.js → chunk-SWPDKLNM.js} +5 -3
- package/dist/chunk-SWPDKLNM.js.map +1 -0
- package/dist/chunk-UCFJ43OS.js +520 -0
- package/dist/chunk-UCFJ43OS.js.map +1 -0
- package/dist/{chunk-VZQXX3WW.js → chunk-W7SLYS3M.js} +80 -16
- package/dist/chunk-W7SLYS3M.js.map +1 -0
- package/dist/{chunk-ANSJPYGI.js → chunk-WV3O5PGX.js} +3 -3
- package/dist/chunk-WV3O5PGX.js.map +1 -0
- package/dist/chunk-XHAO3K7I.js +18 -0
- package/dist/chunk-XHAO3K7I.js.map +1 -0
- package/dist/chunk-XJRJVXJR.js +1050 -0
- package/dist/chunk-XJRJVXJR.js.map +1 -0
- package/dist/chunk-YIL6HTJX.js +663 -0
- package/dist/chunk-YIL6HTJX.js.map +1 -0
- package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
- package/dist/chunk-ZPSZZUWF.js.map +1 -0
- package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-ZRY66LCW.js} +7 -5
- package/dist/context-C3RAP6K5.js +41 -0
- package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-DPQD5H65.js} +6 -5
- package/dist/index.js +947 -203
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-73AQO5WP.js} +18 -9
- package/dist/interactive-assets-73AQO5WP.js.map +1 -0
- package/dist/job-runner.js +166 -111
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.d.ts +2 -0
- package/dist/job-supervisor.js +121 -0
- package/dist/job-supervisor.js.map +1 -0
- package/dist/loader-DQNPDKOX.js +27 -0
- package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-27LJYADQ.js} +6 -5
- package/dist/{mcp-config-YIJIES2C.js → mcp-config-GXSODNCM.js} +4 -3
- package/dist/memory-store-I6Z54VUN.js +51 -0
- package/dist/{model-window-store-YFVNXCIP.js → model-window-store-6VKXXTPJ.js} +9 -4
- package/dist/openai-compatible-IPUNBJ53.js +16 -0
- package/dist/permission-approvals-Q5IMFEZI.js +15 -0
- package/dist/permissions-7Y35X6HZ.js +62 -0
- package/dist/provider-approvals-5AGH3RVT.js +76 -0
- package/dist/sandbox-N2RY72GN.js +42 -0
- package/dist/{scrollback-LUKLIBVI.js → scrollback-GD76GNM4.js} +44 -26
- package/dist/scrollback-GD76GNM4.js.map +1 -0
- package/dist/sdk.d.ts +2739 -2069
- package/dist/sdk.js +77 -42
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-KDBH45AQ.js +43 -0
- package/dist/settings-loader-KDBH45AQ.js.map +1 -0
- package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
- package/dist/settings-removed-3ATWJEF3.js.map +1 -0
- package/dist/shell-selection-Z5WORZU4.js +31 -0
- package/dist/shell-selection-Z5WORZU4.js.map +1 -0
- package/package.json +25 -24
- package/dist/app-MOXXWEOY.js.map +0 -1
- package/dist/chunk-24AXE6SP.js.map +0 -1
- package/dist/chunk-3U5IBM24.js.map +0 -1
- package/dist/chunk-574C6MQR.js.map +0 -1
- package/dist/chunk-5GDZ22YP.js +0 -449
- package/dist/chunk-5GDZ22YP.js.map +0 -1
- package/dist/chunk-5KLVH3PY.js.map +0 -1
- package/dist/chunk-5RDRAO4B.js.map +0 -1
- package/dist/chunk-7N4J557C.js.map +0 -1
- package/dist/chunk-A7JLBK2Y.js +0 -3506
- package/dist/chunk-A7JLBK2Y.js.map +0 -1
- package/dist/chunk-ANSJPYGI.js.map +0 -1
- package/dist/chunk-BTZZUCLI.js +0 -264
- package/dist/chunk-BTZZUCLI.js.map +0 -1
- package/dist/chunk-C7D4NJ2D.js +0 -217
- package/dist/chunk-C7D4NJ2D.js.map +0 -1
- package/dist/chunk-CAGOB2N4.js.map +0 -1
- package/dist/chunk-DGMQEI6Y.js.map +0 -1
- package/dist/chunk-E75IPST5.js.map +0 -1
- package/dist/chunk-EYGD6X2T.js +0 -180
- package/dist/chunk-EYGD6X2T.js.map +0 -1
- package/dist/chunk-GTVB4HEM.js +0 -765
- package/dist/chunk-GTVB4HEM.js.map +0 -1
- package/dist/chunk-J7MIZ5CA.js.map +0 -1
- package/dist/chunk-KWHQ2XXL.js.map +0 -1
- package/dist/chunk-L6LK2QIQ.js +0 -657
- package/dist/chunk-L6LK2QIQ.js.map +0 -1
- package/dist/chunk-MAQXCUR4.js +0 -1007
- package/dist/chunk-MAQXCUR4.js.map +0 -1
- package/dist/chunk-S4XL7HOM.js +0 -402
- package/dist/chunk-S4XL7HOM.js.map +0 -1
- package/dist/chunk-URLQAXTR.js.map +0 -1
- package/dist/chunk-V4BOYR52.js.map +0 -1
- package/dist/chunk-VZQXX3WW.js.map +0 -1
- package/dist/chunk-WKBO5R4O.js.map +0 -1
- package/dist/chunk-XIFFTATU.js.map +0 -1
- package/dist/context-ZC5IFEAX.js +0 -25
- package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
- package/dist/loader-4IDS2RIE.js +0 -15
- package/dist/openai-compatible-RHZUBHV6.js +0 -10
- package/dist/permission-approvals-76PVREFD.js +0 -14
- package/dist/permissions-CHCKTFQQ.js +0 -28
- package/dist/sandbox-CNVAQDA7.js +0 -23
- package/dist/scrollback-LUKLIBVI.js.map +0 -1
- package/dist/settings-loader-M2QP5TX2.js +0 -21
- package/dist/shell-selection-V6QAYXPX.js +0 -17
- package/patches/ink+6.8.0.patch +0 -13
- package/scripts/apply-ink-patch.mjs +0 -29
- package/scripts/ink-patch.mjs +0 -59
- package/scripts/verify-ink-patch.mjs +0 -11
- /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-VAWRQ625.js.map} +0 -0
- /package/dist/{context-ZC5IFEAX.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
- /package/dist/{hook-approvals-CTUFKEJT.js.map → context-C3RAP6K5.js.map} +0 -0
- /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
- /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-DQNPDKOX.js.map} +0 -0
- /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
- /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-GXSODNCM.js.map} +0 -0
- /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-I6Z54VUN.js.map} +0 -0
- /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
- /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
- /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
- /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
- /package/dist/{settings-removed-JWWPT4WA.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
- /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-N2RY72GN.js.map} +0 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import {
|
|
2
|
+
projectFileWriteRefusal
|
|
3
|
+
} from "./chunk-IDYWN37T.js";
|
|
4
|
+
|
|
5
|
+
// src/debug-log.ts
|
|
6
|
+
import {
|
|
7
|
+
appendFileSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
renameSync,
|
|
12
|
+
statSync,
|
|
13
|
+
unlinkSync
|
|
14
|
+
} from "fs";
|
|
15
|
+
import { dirname, join } from "path";
|
|
16
|
+
var DEBUG_ENABLED = process.env.BOOK_DEBUG === "1";
|
|
17
|
+
var UI_DEBUG_ENABLED = process.env.BOOK_DEBUG_UI === "1" || DEBUG_ENABLED;
|
|
18
|
+
var RENDER_DEBUG_ENABLED = process.env.BOOK_DEBUG_RENDER === "1";
|
|
19
|
+
var FLOW_DEBUG_ENABLED = process.env.BOOK_DEBUG_FLOW === "1";
|
|
20
|
+
var debugLogPath;
|
|
21
|
+
var debugLogSizes = /* @__PURE__ */ new Map();
|
|
22
|
+
var refusedDebugLogTargets = /* @__PURE__ */ new Set();
|
|
23
|
+
var DEFAULT_DEBUG_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
24
|
+
var MIN_DEBUG_LOG_MAX_BYTES = 4;
|
|
25
|
+
var DEFAULT_DEBUG_LOG_BACKUPS = 3;
|
|
26
|
+
var DEFAULT_LOCAL_DATA_RETENTION_DAYS = 30;
|
|
27
|
+
function positiveInteger(value, fallback) {
|
|
28
|
+
const parsed = Number(value);
|
|
29
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
30
|
+
}
|
|
31
|
+
function debugLogMaxBytes() {
|
|
32
|
+
return Math.max(
|
|
33
|
+
MIN_DEBUG_LOG_MAX_BYTES,
|
|
34
|
+
positiveInteger(process.env.BOOK_DEBUG_MAX_BYTES, DEFAULT_DEBUG_LOG_MAX_BYTES)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
function rotateDebugLog(target, incomingBytes) {
|
|
38
|
+
if (!existsSync(target)) {
|
|
39
|
+
debugLogSizes.set(target, 0);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const maxBytes = debugLogMaxBytes();
|
|
43
|
+
const currentBytes = debugLogSizes.get(target) ?? statSync(target).size;
|
|
44
|
+
debugLogSizes.set(target, currentBytes);
|
|
45
|
+
if (currentBytes + incomingBytes <= maxBytes) return;
|
|
46
|
+
const backups = positiveInteger(process.env.BOOK_DEBUG_BACKUPS, DEFAULT_DEBUG_LOG_BACKUPS);
|
|
47
|
+
const oldest = `${target}.${backups}`;
|
|
48
|
+
if (existsSync(oldest)) unlinkSync(oldest);
|
|
49
|
+
for (let index = backups - 1; index >= 1; index--) {
|
|
50
|
+
const source = `${target}.${index}`;
|
|
51
|
+
if (existsSync(source)) renameSync(source, `${target}.${index + 1}`);
|
|
52
|
+
}
|
|
53
|
+
renameSync(target, `${target}.1`);
|
|
54
|
+
debugLogSizes.set(target, 0);
|
|
55
|
+
}
|
|
56
|
+
function defaultDebugLogPath() {
|
|
57
|
+
return join(process.env.BOOK_WORKSPACE || process.cwd(), ".book", "debug.log");
|
|
58
|
+
}
|
|
59
|
+
function getDebugLogPath() {
|
|
60
|
+
if (process.env.BOOK_DEBUG_STDERR === "1") return void 0;
|
|
61
|
+
if (process.env.BOOK_DEBUG_FILE) return process.env.BOOK_DEBUG_FILE;
|
|
62
|
+
if (process.stderr.isTTY) return defaultDebugLogPath();
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
function cleanupDebugLogs(days = DEFAULT_LOCAL_DATA_RETENTION_DAYS, target = getDebugLogPath(), now = Date.now()) {
|
|
66
|
+
if (!target) return 0;
|
|
67
|
+
if (target === defaultDebugLogPath() && projectFileWriteRefusal(target)) return 0;
|
|
68
|
+
const cutoff = now - Math.max(1, days) * 864e5;
|
|
69
|
+
const directory = dirname(target);
|
|
70
|
+
const prefix = `${target.split(/[\\/]/).pop()}.`;
|
|
71
|
+
let removed = 0;
|
|
72
|
+
try {
|
|
73
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
74
|
+
if (!entry.isFile() || !entry.name.startsWith(prefix)) continue;
|
|
75
|
+
const path = join(directory, entry.name);
|
|
76
|
+
if (statSync(path).mtimeMs >= cutoff) continue;
|
|
77
|
+
unlinkSync(path);
|
|
78
|
+
debugLogSizes.delete(path);
|
|
79
|
+
removed++;
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
return removed;
|
|
84
|
+
}
|
|
85
|
+
function usableDebugLogTarget(target) {
|
|
86
|
+
if (refusedDebugLogTargets.has(target)) return false;
|
|
87
|
+
const refusal = target === defaultDebugLogPath() ? projectFileWriteRefusal(target) : void 0;
|
|
88
|
+
if (!refusal) return true;
|
|
89
|
+
refusedDebugLogTargets.add(target);
|
|
90
|
+
process.stderr.write(`Debug logging is off for this session: ${target}: ${refusal}.
|
|
91
|
+
`);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
function writeDebugLine(line) {
|
|
95
|
+
const target = debugLogPath ?? (debugLogPath = getDebugLogPath());
|
|
96
|
+
if (!target) {
|
|
97
|
+
process.stderr.write(line);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!usableDebugLogTarget(target)) return;
|
|
101
|
+
try {
|
|
102
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
103
|
+
const lineBytes = Buffer.byteLength(line);
|
|
104
|
+
rotateDebugLog(target, lineBytes);
|
|
105
|
+
appendFileSync(target, line, "utf8");
|
|
106
|
+
debugLogSizes.set(target, (debugLogSizes.get(target) ?? 0) + lineBytes);
|
|
107
|
+
} catch {
|
|
108
|
+
process.stderr.write(line);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function writeSizeBoundedDebugLine(line, maxBytes) {
|
|
112
|
+
if (Buffer.byteLength(line) <= maxBytes) {
|
|
113
|
+
writeDebugLine(line);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
let chunk = "";
|
|
117
|
+
let chunkBytes = 0;
|
|
118
|
+
for (const character of line) {
|
|
119
|
+
const characterBytes = Buffer.byteLength(character);
|
|
120
|
+
if (chunk && chunkBytes + characterBytes > maxBytes) {
|
|
121
|
+
writeDebugLine(chunk);
|
|
122
|
+
chunk = "";
|
|
123
|
+
chunkBytes = 0;
|
|
124
|
+
}
|
|
125
|
+
chunk += character;
|
|
126
|
+
chunkBytes += characterBytes;
|
|
127
|
+
}
|
|
128
|
+
if (chunk) writeDebugLine(chunk);
|
|
129
|
+
}
|
|
130
|
+
var pendingRenderDebug = [];
|
|
131
|
+
var renderDebugFlush = null;
|
|
132
|
+
function writeRenderDebugLine(line) {
|
|
133
|
+
pendingRenderDebug.push(line);
|
|
134
|
+
if (renderDebugFlush !== null) return;
|
|
135
|
+
renderDebugFlush = setImmediate(() => {
|
|
136
|
+
renderDebugFlush = null;
|
|
137
|
+
const batch = pendingRenderDebug;
|
|
138
|
+
pendingRenderDebug = [];
|
|
139
|
+
const maxBytes = debugLogMaxBytes();
|
|
140
|
+
let chunk = "";
|
|
141
|
+
let chunkBytes = 0;
|
|
142
|
+
for (const pendingLine of batch) {
|
|
143
|
+
const lineBytes = Buffer.byteLength(pendingLine);
|
|
144
|
+
if (chunk && chunkBytes + lineBytes > maxBytes) {
|
|
145
|
+
writeDebugLine(chunk);
|
|
146
|
+
chunk = "";
|
|
147
|
+
chunkBytes = 0;
|
|
148
|
+
}
|
|
149
|
+
if (lineBytes > maxBytes) {
|
|
150
|
+
writeSizeBoundedDebugLine(pendingLine, maxBytes);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
chunk += pendingLine;
|
|
154
|
+
chunkBytes += lineBytes;
|
|
155
|
+
}
|
|
156
|
+
if (chunk) writeDebugLine(chunk);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
var namespaceCounters = /* @__PURE__ */ new Map();
|
|
160
|
+
function nextEventId(namespace) {
|
|
161
|
+
const n = (namespaceCounters.get(namespace) ?? 0) + 1;
|
|
162
|
+
namespaceCounters.set(namespace, n);
|
|
163
|
+
return n;
|
|
164
|
+
}
|
|
165
|
+
function isDebugEnabled() {
|
|
166
|
+
return DEBUG_ENABLED;
|
|
167
|
+
}
|
|
168
|
+
function isUiDebugEnabled() {
|
|
169
|
+
return UI_DEBUG_ENABLED;
|
|
170
|
+
}
|
|
171
|
+
function isRenderDebugEnabled() {
|
|
172
|
+
return RENDER_DEBUG_ENABLED;
|
|
173
|
+
}
|
|
174
|
+
function timestamp() {
|
|
175
|
+
const now = /* @__PURE__ */ new Date();
|
|
176
|
+
const h = String(now.getHours()).padStart(2, "0");
|
|
177
|
+
const m = String(now.getMinutes()).padStart(2, "0");
|
|
178
|
+
const s = String(now.getSeconds()).padStart(2, "0");
|
|
179
|
+
const ms = String(now.getMilliseconds()).padStart(3, "0");
|
|
180
|
+
return `${h}:${m}:${s}.${ms}`;
|
|
181
|
+
}
|
|
182
|
+
function truncate(value, max = 80) {
|
|
183
|
+
const single = value.replace(/\s+/g, " ").trim();
|
|
184
|
+
if (single.length <= max) return single;
|
|
185
|
+
return `${single.slice(0, max)}\u2026`;
|
|
186
|
+
}
|
|
187
|
+
function formatValue(a) {
|
|
188
|
+
if (typeof a === "string") return a;
|
|
189
|
+
if (a instanceof Error) return a.message;
|
|
190
|
+
try {
|
|
191
|
+
return JSON.stringify(a);
|
|
192
|
+
} catch {
|
|
193
|
+
return String(a);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function formatMessage(...args) {
|
|
197
|
+
return args.map(formatValue).join(" ");
|
|
198
|
+
}
|
|
199
|
+
function formatMeta(meta) {
|
|
200
|
+
const parts = [];
|
|
201
|
+
for (const [key, value] of Object.entries(meta)) {
|
|
202
|
+
let repr;
|
|
203
|
+
if (typeof value === "string") {
|
|
204
|
+
repr = truncate(value, 120);
|
|
205
|
+
} else if (value === null) {
|
|
206
|
+
repr = "null";
|
|
207
|
+
} else if (value === void 0) {
|
|
208
|
+
repr = "undefined";
|
|
209
|
+
} else {
|
|
210
|
+
try {
|
|
211
|
+
repr = JSON.stringify(value);
|
|
212
|
+
} catch {
|
|
213
|
+
repr = String(value);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
parts.push(`${key}=${repr}`);
|
|
217
|
+
}
|
|
218
|
+
return parts.join(" ");
|
|
219
|
+
}
|
|
220
|
+
function noop() {
|
|
221
|
+
}
|
|
222
|
+
var NOOP_LOGGER = {
|
|
223
|
+
debug: noop,
|
|
224
|
+
info: noop,
|
|
225
|
+
warn: noop,
|
|
226
|
+
event: noop
|
|
227
|
+
};
|
|
228
|
+
function makePrefix(namespace, level) {
|
|
229
|
+
return `[${timestamp()}] [${namespace}] [${level}]`;
|
|
230
|
+
}
|
|
231
|
+
function buildLogger(namespace, write = writeDebugLine) {
|
|
232
|
+
return {
|
|
233
|
+
debug: (...args) => {
|
|
234
|
+
write(`${makePrefix(namespace, "DEBUG")} ${formatMessage(...args)}
|
|
235
|
+
`);
|
|
236
|
+
},
|
|
237
|
+
info: (...args) => {
|
|
238
|
+
write(`${makePrefix(namespace, "INFO")} ${formatMessage(...args)}
|
|
239
|
+
`);
|
|
240
|
+
},
|
|
241
|
+
warn: (...args) => {
|
|
242
|
+
write(`${makePrefix(namespace, "WARN")} ${formatMessage(...args)}
|
|
243
|
+
`);
|
|
244
|
+
},
|
|
245
|
+
event: (event, meta) => {
|
|
246
|
+
const suffix = meta && Object.keys(meta).length > 0 ? ` ${formatMeta(meta)}` : "";
|
|
247
|
+
write(`${makePrefix(namespace, "DEBUG")} ${event}${suffix}
|
|
248
|
+
`);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function createDebugLogger(namespace) {
|
|
253
|
+
if (!DEBUG_ENABLED) return NOOP_LOGGER;
|
|
254
|
+
return buildLogger(namespace);
|
|
255
|
+
}
|
|
256
|
+
function createDebugLoggerWithCounter(namespace) {
|
|
257
|
+
if (!DEBUG_ENABLED) return NOOP_LOGGER;
|
|
258
|
+
const wrap = (level) => (...args) => {
|
|
259
|
+
const id = nextEventId(namespace);
|
|
260
|
+
writeDebugLine(
|
|
261
|
+
`[${timestamp()}] [${namespace}] [${level.toUpperCase()}#${id}] ${formatMessage(...args)}
|
|
262
|
+
`
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
return {
|
|
266
|
+
debug: wrap("debug"),
|
|
267
|
+
info: wrap("info"),
|
|
268
|
+
warn: wrap("warn"),
|
|
269
|
+
event: (event, meta) => {
|
|
270
|
+
const id = nextEventId(namespace);
|
|
271
|
+
const suffix = meta && Object.keys(meta).length > 0 ? ` ${formatMeta(meta)}` : "";
|
|
272
|
+
writeDebugLine(`[${timestamp()}] [${namespace}] [DEBUG#${id}] ${event}${suffix}
|
|
273
|
+
`);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function createUiDebugLogger(namespace) {
|
|
278
|
+
if (!UI_DEBUG_ENABLED) return NOOP_LOGGER;
|
|
279
|
+
return buildLogger(namespace);
|
|
280
|
+
}
|
|
281
|
+
function createRenderDebugLogger(namespace) {
|
|
282
|
+
if (!RENDER_DEBUG_ENABLED) return NOOP_LOGGER;
|
|
283
|
+
return buildLogger(namespace, writeRenderDebugLine);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export {
|
|
287
|
+
DEFAULT_LOCAL_DATA_RETENTION_DAYS,
|
|
288
|
+
positiveInteger,
|
|
289
|
+
getDebugLogPath,
|
|
290
|
+
cleanupDebugLogs,
|
|
291
|
+
isDebugEnabled,
|
|
292
|
+
isUiDebugEnabled,
|
|
293
|
+
isRenderDebugEnabled,
|
|
294
|
+
createDebugLogger,
|
|
295
|
+
createDebugLoggerWithCounter,
|
|
296
|
+
createUiDebugLogger,
|
|
297
|
+
createRenderDebugLogger
|
|
298
|
+
};
|
|
299
|
+
//# sourceMappingURL=chunk-OJBMDM5H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/debug-log.ts"],"sourcesContent":["/**\n * Lightweight debug logger controlled by environment variables.\n *\n * Writes timestamped messages to a log file when stderr is an interactive TTY,\n * because Ink renders to the same terminal and raw stderr writes corrupt the UI.\n * In non-TTY contexts (redirected stderr, CI), logs continue to use stderr.\n * Most functions are no-ops when the relevant env var is not set to '1'.\n *\n * Gated by separate flags so output volume stays controllable:\n * BOOK_DEBUG=1 — provider + agent + TUI lifecycle events\n * BOOK_DEBUG_UI=1 — input handling, modal toggles, permission prompts,\n * component mount/unmount, layout state changes\n * BOOK_DEBUG_RENDER=1 — per-render details (noisy; can flood during streaming)\n * BOOK_DEBUG_FLOW=1 — enter/exit timing around expensive flows\n * BOOK_DEBUG_FILE=path — write logs to a specific file\n * BOOK_DEBUG_STDERR=1 — force stderr even when attached to a TTY\n *\n * `BOOK_DEBUG=1` covers the union of \"core + UI\" (lifecycle). UI hooks read\n * `isUiDebugEnabled()`, which returns true if either BOOK_DEBUG=1 or\n * BOOK_DEBUG_UI=1 so the default mode surfaces UI events too.\n *\n * Usage:\n * import { createDebugLogger } from '../debug-log.js';\n * const log = createDebugLogger('provider');\n * log.debug('Sending request', { model: 'gpt-4' });\n */\n\nimport {\n appendFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n statSync,\n unlinkSync,\n} from 'fs';\nimport { dirname, join } from 'path';\nimport { projectFileWriteRefusal } from './workspace-files.js';\n\n// Core flag — provider, agent loop, and TUI lifecycle.\nconst DEBUG_ENABLED = process.env.BOOK_DEBUG === '1';\n// UI flag — input, modals, permissions, mount/unmount, layout changes.\n// Falls back to core flag so default debug mode surfaces UI events too.\nconst UI_DEBUG_ENABLED = process.env.BOOK_DEBUG_UI === '1' || DEBUG_ENABLED;\n// Noisy per-render flag — opt-in only.\nconst RENDER_DEBUG_ENABLED = process.env.BOOK_DEBUG_RENDER === '1';\n// Flow/tracing flag — enter/exit pairs with timing — opt-in only.\nconst FLOW_DEBUG_ENABLED = process.env.BOOK_DEBUG_FLOW === '1';\n\nlet debugLogPath: string | undefined;\nconst debugLogSizes = new Map<string, number>();\n/** Log targets found unwritable: debug logging to them is off for the rest of the session. */\nconst refusedDebugLogTargets = new Set<string>();\nconst DEFAULT_DEBUG_LOG_MAX_BYTES = 5 * 1024 * 1024;\nconst MIN_DEBUG_LOG_MAX_BYTES = 4;\nconst DEFAULT_DEBUG_LOG_BACKUPS = 3;\nexport const DEFAULT_LOCAL_DATA_RETENTION_DAYS = 30;\n\nexport function positiveInteger(value: string | undefined, fallback: number): number {\n const parsed = Number(value);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;\n}\n\nfunction debugLogMaxBytes(): number {\n // Four bytes is the largest UTF-8 encoding for a single Unicode code point.\n return Math.max(\n MIN_DEBUG_LOG_MAX_BYTES,\n positiveInteger(process.env.BOOK_DEBUG_MAX_BYTES, DEFAULT_DEBUG_LOG_MAX_BYTES),\n );\n}\n\nfunction rotateDebugLog(target: string, incomingBytes: number): void {\n if (!existsSync(target)) {\n debugLogSizes.set(target, 0);\n return;\n }\n const maxBytes = debugLogMaxBytes();\n const currentBytes = debugLogSizes.get(target) ?? statSync(target).size;\n debugLogSizes.set(target, currentBytes);\n if (currentBytes + incomingBytes <= maxBytes) return;\n\n const backups = positiveInteger(process.env.BOOK_DEBUG_BACKUPS, DEFAULT_DEBUG_LOG_BACKUPS);\n const oldest = `${target}.${backups}`;\n if (existsSync(oldest)) unlinkSync(oldest);\n for (let index = backups - 1; index >= 1; index--) {\n const source = `${target}.${index}`;\n if (existsSync(source)) renameSync(source, `${target}.${index + 1}`);\n }\n renameSync(target, `${target}.1`);\n debugLogSizes.set(target, 0);\n}\n\nfunction defaultDebugLogPath(): string {\n return join(process.env.BOOK_WORKSPACE || process.cwd(), '.book', 'debug.log');\n}\n\nexport function getDebugLogPath(): string | undefined {\n if (process.env.BOOK_DEBUG_STDERR === '1') return undefined;\n if (process.env.BOOK_DEBUG_FILE) return process.env.BOOK_DEBUG_FILE;\n if (process.stderr.isTTY) return defaultDebugLogPath();\n return undefined;\n}\n\n/** Remove expired rotated logs while always preserving the active log file. */\nexport function cleanupDebugLogs(\n days = DEFAULT_LOCAL_DATA_RETENTION_DAYS,\n target = getDebugLogPath(),\n now = Date.now(),\n): number {\n if (!target) return 0;\n // The default log's folder is a workspace one, held to the same rule as the log itself.\n if (target === defaultDebugLogPath() && projectFileWriteRefusal(target)) return 0;\n const cutoff = now - Math.max(1, days) * 86_400_000;\n const directory = dirname(target);\n const prefix = `${target.split(/[\\\\/]/).pop()}.`;\n let removed = 0;\n try {\n for (const entry of readdirSync(directory, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.startsWith(prefix)) continue;\n const path = join(directory, entry.name);\n if (statSync(path).mtimeMs >= cutoff) continue;\n unlinkSync(path);\n debugLogSizes.delete(path);\n removed++;\n }\n } catch {\n // Retention cleanup is best effort and must never block startup.\n }\n return removed;\n}\n\n/**\n * The default `.book/debug.log` is one of Book's own workspace files, so it is written only while\n * it is a regular file; otherwise debug logging stops for the session, said once on stderr.\n * `BOOK_DEBUG_FILE` is the user's own choice, a FIFO or `/dev/stderr` included, and is used as\n * given.\n */\nfunction usableDebugLogTarget(target: string): boolean {\n if (refusedDebugLogTargets.has(target)) return false;\n // Checked before every append, not once: a checkout during the session can put something else\n // at the path.\n const refusal = target === defaultDebugLogPath() ? projectFileWriteRefusal(target) : undefined;\n if (!refusal) return true;\n refusedDebugLogTargets.add(target);\n process.stderr.write(`Debug logging is off for this session: ${target}: ${refusal}.\\n`);\n return false;\n}\n\nfunction writeDebugLine(line: string): void {\n const target = debugLogPath ?? (debugLogPath = getDebugLogPath());\n if (!target) {\n process.stderr.write(line);\n return;\n }\n if (!usableDebugLogTarget(target)) return;\n\n try {\n mkdirSync(dirname(target), { recursive: true });\n const lineBytes = Buffer.byteLength(line);\n rotateDebugLog(target, lineBytes);\n appendFileSync(target, line, 'utf8');\n debugLogSizes.set(target, (debugLogSizes.get(target) ?? 0) + lineBytes);\n } catch {\n process.stderr.write(line);\n }\n}\n\nfunction writeSizeBoundedDebugLine(line: string, maxBytes: number): void {\n if (Buffer.byteLength(line) <= maxBytes) {\n writeDebugLine(line);\n return;\n }\n\n // Split oversized render events on UTF-8 character boundaries so one burst\n // cannot bypass the configured active-log cap.\n let chunk = '';\n let chunkBytes = 0;\n for (const character of line) {\n const characterBytes = Buffer.byteLength(character);\n if (chunk && chunkBytes + characterBytes > maxBytes) {\n writeDebugLine(chunk);\n chunk = '';\n chunkBytes = 0;\n }\n chunk += character;\n chunkBytes += characterBytes;\n }\n if (chunk) writeDebugLine(chunk);\n}\n\nlet pendingRenderDebug: string[] = [];\nlet renderDebugFlush: ReturnType<typeof setImmediate> | null = null;\n\nfunction writeRenderDebugLine(line: string): void {\n pendingRenderDebug.push(line);\n if (renderDebugFlush !== null) return;\n renderDebugFlush = setImmediate(() => {\n renderDebugFlush = null;\n const batch = pendingRenderDebug;\n pendingRenderDebug = [];\n const maxBytes = debugLogMaxBytes();\n let chunk = '';\n let chunkBytes = 0;\n\n for (const pendingLine of batch) {\n const lineBytes = Buffer.byteLength(pendingLine);\n if (chunk && chunkBytes + lineBytes > maxBytes) {\n writeDebugLine(chunk);\n chunk = '';\n chunkBytes = 0;\n }\n if (lineBytes > maxBytes) {\n writeSizeBoundedDebugLine(pendingLine, maxBytes);\n continue;\n }\n chunk += pendingLine;\n chunkBytes += lineBytes;\n }\n if (chunk) writeDebugLine(chunk);\n });\n}\n\n// Per-namespace monotonic event counter, used when a logger is created with\n// `createDebugLoggerWithCounter`. Useful for tracing event ordering across\n// concurrent sources (streaming flushes vs. user input).\nconst namespaceCounters = new Map<string, number>();\n\nfunction nextEventId(namespace: string): number {\n const n = (namespaceCounters.get(namespace) ?? 0) + 1;\n namespaceCounters.set(namespace, n);\n return n;\n}\n\n/** True if core debug (provider/agent/TUI lifecycle) is on. */\nexport function isDebugEnabled(): boolean {\n return DEBUG_ENABLED;\n}\n\n/** True if UI debug is on (BOOK_DEBUG=1 already enables this). */\nexport function isUiDebugEnabled(): boolean {\n return UI_DEBUG_ENABLED;\n}\n\n/** True if per-render debug is on (opt-in via BOOK_DEBUG_RENDER=1). */\nexport function isRenderDebugEnabled(): boolean {\n return RENDER_DEBUG_ENABLED;\n}\n\n/** True if flow tracing is on (opt-in via BOOK_DEBUG_FLOW=1). */\nexport function isFlowDebugEnabled(): boolean {\n return FLOW_DEBUG_ENABLED;\n}\n\nfunction timestamp(): string {\n const now = new Date();\n const h = String(now.getHours()).padStart(2, '0');\n const m = String(now.getMinutes()).padStart(2, '0');\n const s = String(now.getSeconds()).padStart(2, '0');\n const ms = String(now.getMilliseconds()).padStart(3, '0');\n return `${h}:${m}:${s}.${ms}`;\n}\n\n/** Truncate a string for safe preview in logs. */\nfunction truncate(value: string, max = 80): string {\n const single = value.replace(/\\s+/g, ' ').trim();\n if (single.length <= max) return single;\n return `${single.slice(0, max)}…`;\n}\n\nfunction formatValue(a: unknown): string {\n if (typeof a === 'string') return a;\n if (a instanceof Error) return a.message;\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n}\n\nfunction formatMessage(...args: unknown[]): string {\n return args.map(formatValue).join(' ');\n}\n\n/** Format a metadata object, truncating any long string values. */\nfunction formatMeta(meta: Record<string, unknown>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(meta)) {\n let repr: string;\n if (typeof value === 'string') {\n repr = truncate(value, 120);\n } else if (value === null) {\n repr = 'null';\n } else if (value === undefined) {\n repr = 'undefined';\n } else {\n try {\n repr = JSON.stringify(value);\n } catch {\n repr = String(value);\n }\n }\n parts.push(`${key}=${repr}`);\n }\n return parts.join(' ');\n}\n\nexport interface DebugLogger {\n debug: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n /** Structured log with key=value metadata (long strings are truncated). */\n event: (event: string, meta?: Record<string, unknown>) => void;\n}\n\nfunction noop(): void {}\n\nconst NOOP_LOGGER: DebugLogger = {\n debug: noop,\n info: noop,\n warn: noop,\n event: noop,\n};\n\nfunction makePrefix(namespace: string, level: string): string {\n return `[${timestamp()}] [${namespace}] [${level}]`;\n}\n\n/** Build a writing logger for an enabled namespace. */\nfunction buildLogger(\n namespace: string,\n write: (line: string) => void = writeDebugLine,\n): DebugLogger {\n return {\n debug: (...args: unknown[]) => {\n write(`${makePrefix(namespace, 'DEBUG')} ${formatMessage(...args)}\\n`);\n },\n info: (...args: unknown[]) => {\n write(`${makePrefix(namespace, 'INFO')} ${formatMessage(...args)}\\n`);\n },\n warn: (...args: unknown[]) => {\n write(`${makePrefix(namespace, 'WARN')} ${formatMessage(...args)}\\n`);\n },\n event: (event, meta) => {\n const suffix = meta && Object.keys(meta).length > 0 ? ` ${formatMeta(meta)}` : '';\n write(`${makePrefix(namespace, 'DEBUG')} ${event}${suffix}\\n`);\n },\n };\n}\n\n/**\n * Create a debug logger gated by `BOOK_DEBUG=1`. Returns a no-op logger when\n * debug is off, so callers can call freely without env checks at call sites.\n */\nexport function createDebugLogger(namespace: string): DebugLogger {\n if (!DEBUG_ENABLED) return NOOP_LOGGER;\n return buildLogger(namespace);\n}\n\n/**\n * Like {@link createDebugLogger} but emits a monotonic event id per namespace\n * in every message, e.g. `[tui:input] [DEBUG#42] key=Escape`. Helpful when\n * tracing event ordering across concurrent sources.\n *\n * Activation matches {@link createDebugLogger}: gated by `BOOK_DEBUG=1`.\n */\nexport function createDebugLoggerWithCounter(namespace: string): DebugLogger {\n if (!DEBUG_ENABLED) return NOOP_LOGGER;\n const wrap =\n (level: 'debug' | 'info' | 'warn') =>\n (...args: unknown[]) => {\n const id = nextEventId(namespace);\n writeDebugLine(\n `[${timestamp()}] [${namespace}] [${level.toUpperCase()}#${id}] ${formatMessage(...args)}\\n`,\n );\n };\n return {\n debug: wrap('debug'),\n info: wrap('info'),\n warn: wrap('warn'),\n event: (event, meta) => {\n const id = nextEventId(namespace);\n const suffix = meta && Object.keys(meta).length > 0 ? ` ${formatMeta(meta)}` : '';\n writeDebugLine(`[${timestamp()}] [${namespace}] [DEBUG#${id}] ${event}${suffix}\\n`);\n },\n };\n}\n\n/**\n * Create a debug logger gated by `BOOK_DEBUG_UI=1` (also active when\n * `BOOK_DEBUG=1`). No-op otherwise. Used for input, modal, permission, and\n * mount/unmount events.\n */\nexport function createUiDebugLogger(namespace: string): DebugLogger {\n if (!UI_DEBUG_ENABLED) return NOOP_LOGGER;\n return buildLogger(namespace);\n}\n\n/**\n * Create a debug logger gated by `BOOK_DEBUG_RENDER=1` only. No-op otherwise.\n * Used for noisy per-render logs. Render logs must be opt-in to keep stderr\n * readable during streaming.\n */\nexport function createRenderDebugLogger(namespace: string): DebugLogger {\n if (!RENDER_DEBUG_ENABLED) return NOOP_LOGGER;\n return buildLogger(namespace, writeRenderDebugLine);\n}\n\n/**\n * Create a debug logger gated by `BOOK_DEBUG_FLOW=1` only. No-op otherwise.\n * Used for enter/exit trace pairs around expensive flows.\n */\nexport function createFlowDebugLogger(namespace: string): DebugLogger {\n if (!FLOW_DEBUG_ENABLED) return NOOP_LOGGER;\n return buildLogger(namespace);\n}\n\n/** Throttle a callback to at most one invocation per `intervalMs`. */\nexport function throttle<A extends unknown[]>(\n fn: (...args: A) => void,\n intervalMs: number,\n): (...args: A) => void {\n let last = 0;\n return (...args: A) => {\n const now = Date.now();\n if (now - last < intervalMs) return;\n last = now;\n fn(...args);\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
updateWorkspaceTrust
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HFB74CLM.js";
|
|
4
4
|
|
|
5
5
|
// src/mcp-approvals.ts
|
|
6
6
|
import { createHash } from "crypto";
|
|
@@ -57,4 +57,4 @@ export {
|
|
|
57
57
|
mcpServersToRecord,
|
|
58
58
|
persistMcpProjectServerChoice
|
|
59
59
|
};
|
|
60
|
-
//# sourceMappingURL=chunk-
|
|
60
|
+
//# sourceMappingURL=chunk-PTDC7ZUP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp-approvals.ts"],"sourcesContent":["/**\n * Trust decisions for MCP servers.\n *\n * Servers declared in a workspace `.mcp.json` are repository-controlled input:\n * connecting one executes an arbitrary command. Hosts therefore require a\n * one-time per-project approval, recorded against a fingerprint of the full\n * connection configuration in the user-global trust store, outside the\n * workspace, where nothing the repository ships can reach it (see\n * `workspace-trust.ts`).\n * A config change after approval invalidates the decision and re-prompts.\n * User-global servers (<BOOK_HOME>/.book/mcp.json) were written by the user\n * and never require approval.\n */\nimport { createHash } from 'crypto';\nimport type { McpProjectServerChoice, McpSettings } from './settings.js';\nimport type { McpServerConfig, ResolvedMcpServer } from './mcp-config.js';\nimport { updateWorkspaceTrust } from './workspace-trust.js';\n\n/** Stable digest of everything the user is asked to trust about a server. */\nexport function mcpServerFingerprint(config: McpServerConfig): string {\n const sortedRecord = (record: Record<string, string> | undefined) =>\n Object.fromEntries(Object.entries(record ?? {}).sort(([a], [b]) => a.localeCompare(b)));\n const canonical = JSON.stringify({\n type: config.type ?? (config.url ? 'http' : 'stdio'),\n command: config.command,\n args: config.args ?? [],\n env: sortedRecord(config.env),\n cwd: config.cwd,\n url: config.url,\n // Values intentionally participate in the digest: changing an auth token\n // or any other header changes what the user has approved. The digest is\n // one-way and neither the UI nor diagnostics render these values.\n headers: sortedRecord(config.headers),\n });\n return createHash('sha256').update(canonical).digest('hex').slice(0, 16);\n}\n\nexport type McpServerApprovalState = 'approved' | 'rejected' | 'unknown';\n\ninterface McpApprovalSettings {\n mcp?: Partial<McpSettings>;\n}\n\nexport function evaluateMcpServerApproval(\n settings: McpApprovalSettings,\n server: ResolvedMcpServer,\n): McpServerApprovalState {\n if (server.source === 'user') return 'approved';\n const choice: McpProjectServerChoice | undefined = settings.mcp?.projectServers?.[server.name];\n if (!choice) return 'unknown';\n if (choice.fingerprint !== mcpServerFingerprint(server.config)) return 'unknown';\n return choice.choice;\n}\n\nexport interface McpApprovalPartition {\n /** User-global servers plus approved project servers — safe to connect. */\n allowed: ResolvedMcpServer[];\n /** Project servers with no (valid) recorded decision — prompt or skip. */\n pending: ResolvedMcpServer[];\n /** Project servers the user rejected. */\n rejected: ResolvedMcpServer[];\n}\n\nexport function partitionMcpServersByApproval(\n servers: ResolvedMcpServer[],\n settings: McpApprovalSettings,\n): McpApprovalPartition {\n const partition: McpApprovalPartition = { allowed: [], pending: [], rejected: [] };\n for (const server of servers) {\n const state = evaluateMcpServerApproval(settings, server);\n if (state === 'approved') partition.allowed.push(server);\n else if (state === 'rejected') partition.rejected.push(server);\n else partition.pending.push(server);\n }\n return partition;\n}\n\nexport function mcpServersToRecord(servers: ResolvedMcpServer[]): Record<string, McpServerConfig> {\n return Object.fromEntries(servers.map((server) => [server.name, server.config]));\n}\n\n/**\n * Record an approve/reject decision for one server, leaving every other\n * recorded decision — in this workspace and in every other — untouched.\n */\nexport function persistMcpProjectServerChoice(\n workspace: string,\n name: string,\n fingerprint: string,\n choice: McpProjectServerChoice['choice'],\n options: { trustStorePath?: string } = {},\n): { ok: boolean; error?: string } {\n return updateWorkspaceTrust(\n workspace,\n (trust) => {\n trust.mcpServers[name] = { fingerprint, choice } satisfies McpProjectServerChoice;\n },\n options.trustStorePath,\n );\n}\n"],"mappings":";;;;;AAaA,SAAS,kBAAkB;AAMpB,SAAS,qBAAqB,QAAiC;AACpE,QAAM,eAAe,CAAC,WACpB,OAAO,YAAY,OAAO,QAAQ,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;AACxF,QAAM,YAAY,KAAK,UAAU;AAAA,IAC/B,MAAM,OAAO,SAAS,OAAO,MAAM,SAAS;AAAA,IAC5C,SAAS,OAAO;AAAA,IAChB,MAAM,OAAO,QAAQ,CAAC;AAAA,IACtB,KAAK,aAAa,OAAO,GAAG;AAAA,IAC5B,KAAK,OAAO;AAAA,IACZ,KAAK,OAAO;AAAA;AAAA;AAAA;AAAA,IAIZ,SAAS,aAAa,OAAO,OAAO;AAAA,EACtC,CAAC;AACD,SAAO,WAAW,QAAQ,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACzE;AAQO,SAAS,0BACd,UACA,QACwB;AACxB,MAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,QAAM,SAA6C,SAAS,KAAK,iBAAiB,OAAO,IAAI;AAC7F,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,gBAAgB,qBAAqB,OAAO,MAAM,EAAG,QAAO;AACvE,SAAO,OAAO;AAChB;AAWO,SAAS,8BACd,SACA,UACsB;AACtB,QAAM,YAAkC,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE;AACjF,aAAW,UAAU,SAAS;AAC5B,UAAM,QAAQ,0BAA0B,UAAU,MAAM;AACxD,QAAI,UAAU,WAAY,WAAU,QAAQ,KAAK,MAAM;AAAA,aAC9C,UAAU,WAAY,WAAU,SAAS,KAAK,MAAM;AAAA,QACxD,WAAU,QAAQ,KAAK,MAAM;AAAA,EACpC;AACA,SAAO;AACT;AAEO,SAAS,mBAAmB,SAA+D;AAChG,SAAO,OAAO,YAAY,QAAQ,IAAI,CAAC,WAAW,CAAC,OAAO,MAAM,OAAO,MAAM,CAAC,CAAC;AACjF;AAMO,SAAS,8BACd,WACA,MACA,aACA,QACA,UAAuC,CAAC,GACP;AACjC,SAAO;AAAA,IACL;AAAA,IACA,CAAC,UAAU;AACT,YAAM,WAAW,IAAI,IAAI,EAAE,aAAa,OAAO;AAAA,IACjD;AAAA,IACA,QAAQ;AAAA,EACV;AACF;","names":[]}
|